├── Makefile ├── code ├── elfload.h ├── fakestack.h ├── ptinject.h └── shargs.h ├── icrt ├── icrt.h ├── icrt_asm.h ├── icrt_mem.h ├── icrt_std.h ├── icrt_syscall.h └── icrt_utils.h ├── mandibule.c ├── readme.md └── samples ├── target.c └── toinject.c /Makefile: -------------------------------------------------------------------------------- 1 | # ======================================================================== # 2 | # author: ixty 2018 # 3 | # project: mandibule # 4 | # licence: beerware # 5 | # ======================================================================== # 6 | 7 | # mandibule makefile 8 | 9 | CFLAGS += -D_GNU_SOURCE -std=gnu99 10 | CFLAGS += -static-libgcc -lgcc 11 | CFLAGS += -I icrt/ -I code/ 12 | CFLAGS += -fno-common -fno-stack-protector -fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables -fno-unwind-tables 13 | # CFLAGS += -Wall -Werror -Wpedantic 14 | PORTABLE = -pie -fPIE -fno-builtin 15 | MACHINE = $(shell uname -m) 16 | 17 | # automaticaly target current arch 18 | all: $(MACHINE) 19 | 20 | # PC 32 bits 21 | # add -m32 so we can compile it on amd64 as well 22 | i386: x86 23 | i486: x86 24 | i686: x86 25 | x86: clean 26 | $(CC) $(CFLAGS) $(PORTABLE) -nostdlib -m32 -o mandibule mandibule.c 27 | $(CC) $(CFLAGS) $(PORTABLE) -m32 -o toinject samples/toinject.c 28 | $(CC) $(CFLAGS) -m32 -o target samples/target.c 29 | 30 | # PC 64 bits 31 | amd64: x86_64 32 | x86_64: clean 33 | $(CC) $(CFLAGS) $(PORTABLE) -nostdlib -o mandibule mandibule.c 34 | $(CC) $(CFLAGS) $(PORTABLE) -o toinject samples/toinject.c 35 | $(CC) $(CFLAGS) -o target samples/target.c 36 | 37 | 38 | # ARM 32 bits 39 | # unable to compile arm without nostdlib (uidiv divmod ...) 40 | arm: armv7l 41 | armhf: armv7l 42 | armv7: armv7l 43 | armv7l: clean 44 | $(CC) $(CFLAGS) $(PORTABLE) -nostartfiles -o mandibule mandibule.c 45 | $(CC) $(CFLAGS) $(PORTABLE) -o toinject samples/toinject.c 46 | $(CC) $(CFLAGS) -o target samples/target.c 47 | 48 | # ARM 64 bits 49 | arm64: aarch64 50 | armv8: aarch64 51 | armv8l: aarch64 52 | aarch64: clean 53 | $(CC) $(CFLAGS) $(PORTABLE) -nostdlib -o mandibule mandibule.c 54 | $(CC) $(CFLAGS) $(PORTABLE) -o toinject samples/toinject.c 55 | $(CC) $(CFLAGS) -o target samples/target.c 56 | 57 | clean: 58 | rm -rf mandibule target toinject 59 | 60 | -------------------------------------------------------------------------------- /code/elfload.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: mandibule // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // code to manually map an executable ELF in local memory 8 | // uses mmap & mprotect to map segments in memory 9 | // this code does not process relocations 10 | // this code loads the loader to handle dependencies 11 | 12 | #ifndef _ELFLOAD_H 13 | #define _ELFLOAD_H 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #if defined(__i386__) || defined(__arm__) 20 | // 32 bits elf 21 | #define elf_ehdr Elf32_Ehdr 22 | #define elf_phdr Elf32_Phdr 23 | 24 | #elif defined(__x86_64__) || defined(__aarch64__) 25 | // 64 bits elf 26 | #define elf_ehdr Elf64_Ehdr 27 | #define elf_phdr Elf64_Phdr 28 | #endif 29 | 30 | // some defines.. 31 | #define PAGE_SIZE 0x1000 32 | #define MOD_OFFSET_NEXT 0x10000 33 | #define MOD_OFFSET_BIN 0x100000 34 | 35 | #define ALIGN_PAGE_UP(x) do { if((x) % PAGE_SIZE) (x) += (PAGE_SIZE - ((x) % PAGE_SIZE)); } while(0) 36 | #define ALIGN_PAGE_DOWN(x) do { if((x) % PAGE_SIZE) (x) -= ((x) % PAGE_SIZE); } while(0) 37 | 38 | // utility to set AUX values 39 | static inline int set_auxv(unsigned long * auxv, unsigned long at_type, unsigned long at_val) 40 | { 41 | int i = 0; 42 | while(auxv[i] && auxv[i] != at_type) 43 | i += 2; 44 | 45 | if(!auxv[i]) 46 | { 47 | printf("> error setting auxv[%d] to 0x%llx\n", at_type, at_val); 48 | return -1; 49 | } 50 | 51 | auxv[i+1] = at_val; 52 | printf("> set auxv[%d] to 0x%llx\n", at_type, at_val); 53 | return 0; 54 | } 55 | 56 | // load a single segment in memory & set perms 57 | static inline int load_segment(uint8_t * elf, elf_ehdr * ehdr, elf_phdr * phdr, unsigned long base_off) 58 | { 59 | unsigned long seg_addr = phdr->p_vaddr + base_off; 60 | unsigned long seg_len = phdr->p_memsz; 61 | unsigned long seg_off = seg_addr & (PAGE_SIZE - 1); 62 | int prot = 0; 63 | long addr; 64 | 65 | // align segment 66 | ALIGN_PAGE_UP(seg_len); 67 | 68 | // get memory at fixed addr 69 | addr = (unsigned long)_mmap((void*)(seg_addr - seg_off), seg_len + seg_off, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 70 | 71 | // all good? 72 | printf("> load segment addr 0x%llx len 0x%llx => 0x%llx\n", seg_addr, seg_len, addr); 73 | if(addr != seg_addr - seg_off) 74 | return -1; 75 | 76 | // load up the segment with code/data 77 | memcpy((void*)seg_addr, elf + phdr->p_offset, phdr->p_filesz); 78 | 79 | // set correct permission 80 | if(phdr->p_flags & PF_R) prot |= PROT_READ; 81 | if(phdr->p_flags & PF_W) prot |= PROT_WRITE; 82 | if(phdr->p_flags & PF_X) prot |= PROT_EXEC; 83 | if(_mprotect((void*)(seg_addr - seg_off), seg_len + seg_off, prot) < 0) 84 | return -1; 85 | 86 | // all good 87 | return 0; 88 | } 89 | 90 | // load an elf in memory at specified base address 91 | // will load and run interpreter if any 92 | static inline int map_elf(char * path, unsigned long base_mod, unsigned long * auxv, unsigned long * out_eop) 93 | { 94 | uint8_t * elf_buf = NULL; 95 | size_t elf_len = 0; 96 | unsigned long base_off = 0; 97 | unsigned long base_next = 0; 98 | unsigned long base_seg = 0; 99 | unsigned long base_intp = 0; 100 | unsigned long eop_elf = 0; 101 | unsigned long eop_ldr = 0; 102 | elf_ehdr * ehdr; 103 | elf_phdr * phdr; 104 | 105 | printf("> reading elf file '%s'\n", path); 106 | if(read_file(path, &elf_buf, &elf_len)) 107 | { 108 | printf("> read_file failed\n"); 109 | return -1; 110 | } 111 | 112 | printf("> loading elf at: 0x%llx\n", base_mod); 113 | ehdr = (elf_ehdr *)elf_buf; 114 | if(ehdr->e_type == ET_DYN) 115 | base_off = base_mod; 116 | 117 | eop_elf = base_off + ehdr->e_entry; 118 | 119 | // load segments 120 | for(int i=0; ie_phnum; i++) 121 | { 122 | phdr = (elf_phdr *)(elf_buf + ehdr->e_phoff + i * ehdr->e_phentsize); 123 | // printf("> seg[%d] load: %d addr 0x%llx size 0x%llx\n", i, phdr->p_type == PT_LOAD, phdr->p_vaddr, phdr->p_memsz); 124 | if(phdr->p_type == PT_LOAD && load_segment(elf_buf, ehdr, phdr, base_off)) 125 | return -1; 126 | if(!base_seg) 127 | base_seg = phdr->p_vaddr; 128 | base_next = phdr->p_vaddr + phdr->p_memsz > base_next ? phdr->p_vaddr + phdr->p_memsz : base_next; 129 | } 130 | 131 | ALIGN_PAGE_DOWN(base_seg); 132 | 133 | if(ehdr->e_type == ET_DYN) 134 | base_seg += base_off; 135 | // printf("> program base: 0x%llx\n", base_seg); 136 | 137 | base_next += MOD_OFFSET_NEXT; 138 | ALIGN_PAGE_UP(base_next); 139 | 140 | printf("> max vaddr 0x%llx\n", base_mod + base_next); 141 | 142 | // load interp 143 | for(int i=0; ie_phnum; i++) 144 | { 145 | char * interp; 146 | 147 | phdr = (elf_phdr *)(elf_buf + ehdr->e_phoff + i * ehdr->e_phentsize); 148 | 149 | if(phdr->p_type != PT_INTERP) 150 | continue; 151 | 152 | base_intp = base_mod + base_next; 153 | interp = (char*)(ehdr->e_type == ET_DYN ? base_seg : 0) + phdr->p_vaddr; 154 | 155 | printf("> loading interp '%s'\n", interp); 156 | if(map_elf(interp, base_intp, NULL, &eop_ldr)) 157 | return -1; 158 | } 159 | 160 | // set auxv phdr / phent / phnum / pagesz / entry 161 | if(auxv) 162 | { 163 | printf("> setting auxv\n"); 164 | set_auxv(auxv, AT_PHDR, base_seg + ehdr->e_phoff); 165 | set_auxv(auxv, AT_PHNUM, ehdr->e_phnum); 166 | set_auxv(auxv, AT_ENTRY, eop_elf); 167 | set_auxv(auxv, AT_BASE, base_seg); 168 | } 169 | 170 | *out_eop = eop_ldr ? eop_ldr : eop_elf; 171 | printf("> eop 0x%llx\n", *out_eop); 172 | return 0; 173 | } 174 | 175 | 176 | #endif 177 | -------------------------------------------------------------------------------- /code/fakestack.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: mandibule // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // this code is used to generate a stack with auxv envv argv argc 8 | // this stack will be given to ld-linux after our manual ELF mapping 9 | 10 | #ifndef _FAKESTACK_H 11 | #define _FAKESTACK_H 12 | 13 | // utilities to build a fake "pristine stack" as if we were just loaded by the kernel 14 | 15 | #define FSTACK_PUSH_STR(sp, s) \ 16 | { \ 17 | unsigned long l = strlen(s) + 1; \ 18 | unsigned long a = (unsigned long)sp - l; \ 19 | while((a % sizeof(unsigned long)) != 0) \ 20 | a -= 1; \ 21 | memcpy((void*)a, s, l); \ 22 | sp = (void*)a; \ 23 | } 24 | 25 | #define FSTACK_PUSH_LONG(sp, n) \ 26 | { \ 27 | unsigned long l = sizeof(unsigned long); \ 28 | unsigned long v = n; \ 29 | sp -= l; \ 30 | memcpy(sp, &v, l); \ 31 | } 32 | 33 | #define FSTACK_PUSH_AUXV(sp, auxv) \ 34 | { \ 35 | unsigned long * a = auxv; \ 36 | FSTACK_PUSH_LONG(sp, 0); \ 37 | FSTACK_PUSH_LONG(sp, 0); \ 38 | while(*a) \ 39 | { \ 40 | FSTACK_PUSH_LONG(sp, a[1]); \ 41 | FSTACK_PUSH_LONG(sp, a[0]); \ 42 | a += 2; \ 43 | } \ 44 | } 45 | 46 | static inline uint8_t * fake_stack(uint8_t * sp, int ac, char ** av, char ** env, unsigned long * auxv) 47 | { 48 | uint8_t * env_ptrs[256]; 49 | int env_max = 0; 50 | char * av_0 = NULL; 51 | memset(env_ptrs, 0, sizeof(env_ptrs)); 52 | 53 | // align stack 54 | FSTACK_PUSH_STR(sp, ""); 55 | 56 | // copy original env 57 | while(*env && env_max < 254) 58 | { 59 | FSTACK_PUSH_STR(sp, *env); 60 | env_ptrs[env_max++] = sp; 61 | env ++; 62 | } 63 | 64 | // add to envdata 65 | FSTACK_PUSH_STR(sp, "MANMAP=1"); 66 | env_ptrs[env_max++] = sp; 67 | 68 | // argv data 69 | for(int i=0; i 15 | #include 16 | #include 17 | 18 | // ======================================================================== // 19 | // arch specific defines 20 | // ======================================================================== // 21 | #if defined(__i386__) 22 | #define REG_TYPE user_regs_struct 23 | #define REG_PC eip 24 | #define PC_OFF 2 25 | #define REG_SYSCALL orig_eax 26 | 27 | #elif defined(__x86_64__) 28 | #define REG_TYPE user_regs_struct 29 | #define REG_PC rip 30 | #define PC_OFF 2 31 | #define REG_SYSCALL orig_rax 32 | 33 | #elif defined(__arm__) 34 | #define REG_TYPE user_regs 35 | #define REG_PC uregs[15] 36 | #define PC_OFF 4 37 | #define REG_SYSCALL uregs[7] 38 | 39 | #elif defined(__aarch64__) 40 | #define REG_TYPE user_regs_struct 41 | #define REG_PC pc 42 | #define PC_OFF 4 43 | #define REG_SYSCALL regs[8] 44 | 45 | #else 46 | #error "unknown arch" 47 | #endif 48 | 49 | 50 | // ======================================================================== // 51 | // tools 52 | // ======================================================================== // 53 | 54 | // error macro 55 | #define _pt_fail(...) do { printf(__VA_ARGS__); return -1; } while(0) 56 | 57 | // returns the first executable segment of a process 58 | int _pt_getxzone(pid_t pid, unsigned long * addr, size_t * size) 59 | { 60 | size_t min_size = *size; 61 | 62 | if(get_section(pid, "r-xp", addr, size) == 0 && *size > min_size) 63 | return 0; 64 | if(get_section(pid, "rwxp", addr, size) == 0 && *size > min_size) 65 | return 0; 66 | _pt_fail("> no executable section is large enough :/\n"); 67 | } 68 | 69 | // read remote memory lword by lword 70 | int _pt_read(int pid, void * addr, void * dst, size_t len) 71 | { 72 | size_t n = 0; 73 | long r; 74 | 75 | while(n < len) 76 | { 77 | if( (r = _ptrace(PTRACE_PEEKTEXT, pid, (uint8_t*)addr + n, (uint8_t*)dst + n)) < 0) 78 | _pt_fail("_pt_read error %d\n", r); 79 | n += sizeof(long); 80 | } 81 | return 0; 82 | } 83 | 84 | // write remote memory lword by lword 85 | int _pt_write(int pid, void * addr, void * src, size_t len) 86 | { 87 | size_t n = 0; 88 | long r; 89 | 90 | while(n < len) 91 | { 92 | if( (r = _ptrace(PTRACE_POKETEXT, pid, (uint8_t*)addr + n, (void*)*(long*)((uint8_t*)src + n))) < 0) 93 | _pt_fail("_pt_write error %d", r); 94 | n += sizeof(long); 95 | } 96 | return 0; 97 | } 98 | 99 | // arm64 doesnt support PTRACE_GETREGS, so we use getregset 100 | int _pt_getregs(int pid, struct REG_TYPE * regs) 101 | { 102 | struct iovec io; 103 | io.iov_base = regs; 104 | io.iov_len = sizeof(*regs); 105 | 106 | if(_ptrace(PTRACE_GETREGSET, pid, (void*)NT_PRSTATUS, (void*)&io) == -1) 107 | _pt_fail("> PTRACE_GETREGSET error\n"); 108 | return 0; 109 | } 110 | 111 | // arm64 doesnt support PTRACE_GETREGS, so we use getregset 112 | int _pt_setregs(int pid, struct REG_TYPE * regs) 113 | { 114 | struct iovec io; 115 | io.iov_base = regs; 116 | io.iov_len = sizeof(*regs); 117 | 118 | if(_ptrace(PTRACE_SETREGSET, pid, (void*)NT_PRSTATUS, (void*)&io) == -1) 119 | _pt_fail("> PTRACE_SETREGSET error\n"); 120 | return 0; 121 | } 122 | 123 | int _pt_cancel_syscall(int pid) 124 | { 125 | #ifdef __arm__ 126 | if(_ptrace(PTRACE_SET_SYSCALL, pid, NULL, (void*)-1) < 0) 127 | _pt_fail("> PTRACE_SET_SYSCALL err\n"); 128 | 129 | #elif __aarch64__ 130 | struct iovec iov; 131 | long sysnbr = -1; 132 | 133 | iov.iov_base = &sysnbr; 134 | iov.iov_len = sizeof(long); 135 | if(_ptrace(PTRACE_SETREGSET, pid, (void*)NT_ARM_SYSTEM_CALL, &iov) < 0) 136 | _pt_fail("> PTRACE_SETREGSET NT_ARM_SYSTEM_CALL err\n"); 137 | #else 138 | // nothing specific to do on x86 & amd64 139 | #endif 140 | return 0; 141 | } 142 | 143 | // inject shellcode via ptrace into remote process 144 | int pt_inject(pid_t pid, uint8_t * sc_buf, size_t sc_len, size_t start_offset) 145 | { 146 | struct REG_TYPE regs; 147 | struct REG_TYPE regs_backup; 148 | unsigned long rvm_a = 0; 149 | size_t rvm_l = sc_len; 150 | uint8_t * mem_backup = NULL; 151 | int s; 152 | 153 | memset(®s, 0, sizeof(regs)); 154 | memset(®s_backup, 0, sizeof(regs_backup)); 155 | 156 | // get executable section large enough for our injected code 157 | if(_pt_getxzone(pid, &rvm_a, &rvm_l) < 0) 158 | return -1; 159 | printf("> shellcode injection addr: 0x%lx size: 0x%lx (available: 0x%lx)\n", rvm_a, sc_len, rvm_l); 160 | 161 | // attach process & wait for break 162 | if(_ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) 163 | _pt_fail("> PTRACE_ATTACH error"); 164 | if(_wait4(pid, &s, WUNTRACED, NULL) != pid) 165 | _pt_fail("> wait4(%d) error\n", pid); 166 | printf("> success attaching to pid %d\n", pid); 167 | 168 | // backup registers 169 | if(_pt_getregs(pid, ®s)) 170 | return -1; 171 | memcpy(®s_backup, ®s, sizeof(struct REG_TYPE)); 172 | 173 | // backup memory 174 | if(!(mem_backup = malloc(sc_len + sizeof(long)))) 175 | _pt_fail("> malloc failed to allocate memory for remote mem backup\n"); 176 | 177 | if(_pt_read(pid, (void*)rvm_a, mem_backup, sc_len) < 0) 178 | _pt_fail("> failed to read remote memory\n"); 179 | printf("> backed up mem & registers\n"); 180 | 181 | // inject shellcode 182 | if(_pt_write(pid, (void*)rvm_a, sc_buf, sc_len)) 183 | return -1; 184 | printf("> injected shellcode at 0x%lx\n", rvm_a); 185 | 186 | // adjust PC / eip / rip to our injected code 187 | regs.REG_PC = rvm_a + PC_OFF + start_offset; 188 | if(_pt_setregs(pid, ®s)) 189 | return -1; 190 | 191 | // execute code now 192 | printf("> running shellcode..\n"); 193 | 194 | // wait until the target process calls exit() / exit_group() 195 | while(1) 196 | { 197 | if(_ptrace(PTRACE_SYSCALL, pid, NULL, NULL) < 0) 198 | _pt_fail("> PTRACE_SYSCALL error\n"); 199 | 200 | if(_wait4(pid, &s, 0, NULL) < 0 || WIFEXITED(s)) 201 | _pt_fail("> wait4 error\n"); 202 | 203 | if(_pt_getregs(pid, ®s)) 204 | return -1; 205 | 206 | if(regs.REG_SYSCALL == SYS_exit || regs.REG_SYSCALL == SYS_exit_group) 207 | { 208 | _pt_cancel_syscall(pid); 209 | break; 210 | } 211 | 212 | } 213 | 214 | printf("> shellcode executed!\n"); 215 | 216 | // restore reg & mem backup 217 | if(_pt_write(pid, (void*)rvm_a, mem_backup, sc_len)) 218 | return -1; 219 | if(_pt_setregs(pid, ®s_backup)) 220 | return -1; 221 | printf("> restored memory & registers\n"); 222 | 223 | // all done, detach now 224 | if(_ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) 225 | _pt_fail("> _pt_detach error\n"); 226 | 227 | return 0; 228 | } 229 | 230 | #endif 231 | -------------------------------------------------------------------------------- /code/shargs.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: mandibule // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | #ifndef _ARGS_H 8 | #define _ARGS_H 9 | 10 | // code to build / parse / manipulate a shared arguments structure 11 | // used to pass info from injector to injected code 12 | // we hijack the ELF header (+ section header ...) to pass arguments to the injected copy of our code 13 | 14 | // fwd declarations 15 | void usage(char * argv0, char * msg); 16 | 17 | // small error macro 18 | #define _ashared_error(...) do { printf(__VA_ARGS__); _exit(1); } while(0) 19 | 20 | // shared arguments between injector & injectee 21 | typedef struct ashared_s 22 | { 23 | // internals 24 | size_t size_max; // total size in bytes of header + data 25 | size_t size_used; // currently used memory 26 | 27 | // payload 28 | unsigned long pid; // pid to inject into 29 | unsigned long base_addr; // address where we want to load binary in memory - 0 for auto 30 | int count_arg; // number of arguments 31 | int count_env; // number of env values 32 | char data[]; // data where args & envs are stored, list of null terminated strings 33 | } ashared_t; 34 | 35 | // allocates memory & prepares structure 36 | ashared_t * _ashared_new(size_t size) 37 | { 38 | ashared_t * a = (ashared_t *)malloc(size); 39 | if(!a) 40 | return NULL; 41 | 42 | memset(a, 0, size); 43 | a->base_addr = -1; 44 | a->size_max = size; 45 | a->size_used = sizeof(ashared_t); 46 | return a; 47 | } 48 | 49 | // add a string (either arg or env) to our shared data struct 50 | int _ashared_add(ashared_t * a, char * arg, int is_arg_not_env) 51 | { 52 | // size of data to be added (including null byte) 53 | size_t l = strlen(arg) + 1; 54 | // trying to add an argument after we started adding env? 55 | if(is_arg_not_env && a->count_env) 56 | return -1; 57 | 58 | // check that we have the required space 59 | if(a->size_max - a->size_used < l) 60 | return -1; 61 | 62 | char * p = a->data + a->size_used - sizeof(ashared_t); 63 | memcpy(p, arg, l); 64 | a->size_used += l; 65 | if(is_arg_not_env) 66 | a->count_arg += 1; 67 | else 68 | a->count_env += 1; 69 | return 0; 70 | } 71 | 72 | // get an envvar or argval from index 73 | char * _ashared_get(ashared_t * a, int ind, int is_arg_not_env) 74 | { 75 | char * p = a->data; 76 | 77 | // sanity checks 78 | if(is_arg_not_env && ind >= a->count_arg) 79 | return NULL; 80 | if(!is_arg_not_env && ind >= a->count_env) 81 | return NULL; 82 | 83 | // convert env index to string (arg + env) index in data 84 | if(!is_arg_not_env) 85 | ind += a->count_arg; 86 | 87 | // enumerate argvs 88 | for(int i=0; icount_arg + a->count_env; i++) 89 | { 90 | // found what we're looking for? 91 | if(i == ind) return p; 92 | 93 | // goto next string 94 | while(*p) p++; 95 | p++; 96 | } 97 | 98 | return NULL; 99 | } 100 | 101 | // print recap of settings 102 | void _ashared_print(ashared_t * args) 103 | { 104 | printf("> target pid: %d\n", args->pid); 105 | 106 | for(int i=0; icount_arg; i++) 107 | printf("> arg[%d]: %s\n", i, _ashared_get(args, i, 1)); 108 | for(int i=0; icount_env; i++) 109 | printf("> env[%d]: %s\n", i, _ashared_get(args, i, 0)); 110 | if(args->base_addr != (unsigned long)-1) 111 | printf("> base address: 0x%lx\n", args->base_addr); 112 | 113 | printf("> args size: %d\n", args->size_used); 114 | } 115 | 116 | // parse argc & argv to fill info 117 | ashared_t * _ashared_parse(int ac, char ** av) 118 | { 119 | // allocate struct to pass info to injected code in remote process 120 | ashared_t * args = _ashared_new(0x1000); 121 | int optind = 1; 122 | if(!args) 123 | _ashared_error("> error allocating memory for arguments\n"); 124 | 125 | // parse arguments 126 | if(ac < 3) 127 | usage(av[0], "at least 2 arguments are required ( + )"); 128 | 129 | // add argv[0] (path of elf to inject) 130 | if(_ashared_add(args, av[optind++], 1) < 0) 131 | _ashared_error("> error setting elf path (too long?)\n"); 132 | 133 | // add arguments 134 | while(!strcmp(av[optind], "-a")) 135 | { 136 | if(optind + 1 + 1 >= ac) 137 | usage(av[0], "missing string for option -a"); 138 | if(_ashared_add(args, av[optind+1], 1) < 0) 139 | _ashared_error("> error setting argument '%s'\n", av[optind+1]); 140 | optind += 2; 141 | } 142 | 143 | // add environment 144 | while(!strcmp(av[optind], "-e")) 145 | { 146 | if(optind + 1 + 1 >= ac) 147 | usage(av[0], "missing string for option -e"); 148 | if(_ashared_add(args, av[optind + 1], 0) < 0) 149 | _ashared_error("> error setting envvar '%s'\n", av[optind + 1]); 150 | optind += 2; 151 | } 152 | 153 | // specify memory addr ? 154 | if(!strcmp(av[optind], "-m")) 155 | { 156 | if(optind + 1 + 1 >= ac) 157 | usage(av[0], "missing address for option -m"); 158 | args->base_addr = strtoul(av[optind + 1], NULL, 0); 159 | optind += 2; 160 | } 161 | 162 | // and finally, the pid to inject into 163 | args->pid = strtoul(av[optind], NULL, 10); 164 | 165 | // print a recap now 166 | _ashared_print(args); 167 | 168 | // parsing finished - trim struct 169 | args->size_max = args->size_used; 170 | return args; 171 | } 172 | 173 | #endif 174 | -------------------------------------------------------------------------------- /icrt/icrt.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: inline c runtime library // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | #ifndef _ICRT_H 8 | #define _ICRT_H 9 | 10 | // small inline C runtime library 11 | // defines its syscalls using inline assembly 12 | // defines a few standard functions str* mem* ... 13 | 14 | #include "icrt_asm.h" 15 | #include "icrt_syscall.h" 16 | #include "icrt_std.h" 17 | #include "icrt_utils.h" 18 | #include "icrt_mem.h" 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /icrt/icrt_asm.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: inline c runtime library // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | #ifndef _ICRT_ASM_H 8 | #define _ICRT_ASM_H 9 | 10 | // in there we define a few inline asm utilities: 11 | // 32/64 bits define 12 | // macro to call a func with stack pointer as argument 13 | // macro to define the injection entrypoint (nops + call) 14 | // macro to set SP & jump to specified address 15 | 16 | // arch currently supported: 17 | // x86 18 | // x86_64 19 | // arm 20 | // arm64 21 | 22 | #ifdef __i386__ 23 | #define BITS32 24 | 25 | // x86: code to call a function with stack pointer as argument 26 | #define CALL_SP(addr) \ 27 | asm volatile("push %%esp; call *%%eax;" : : "a"(addr) ); 28 | 29 | // x86: entry point of code injected into remote process 30 | #define INJ_ENTRY(name, func) \ 31 | asm(".text; .global " #name "; " #name ":; nop; nop; call " #func "; int3;"); \ 32 | extern void name(void); 33 | 34 | // x86: code to set stack pointer and jump to address 35 | #define FIX_SP_JMP(stack, addr) \ 36 | asm volatile("xchg %%esp, %0; jmp *%%eax;" : "=r"(stack) : "0"(stack), "a"(addr) ); 37 | 38 | #elif __x86_64__ 39 | #define BITS64 40 | 41 | // x86_64: code to call a function with stack pointer as argument 42 | #define CALL_SP(addr) \ 43 | asm volatile("mov %%rsp, %%rdi; call *%%rax;" : : "a"(addr) ); 44 | 45 | // x86_64: entry point of code injected into remote process 46 | #define INJ_ENTRY(name, func) \ 47 | asm(".text; .global " #name "; " #name ":; nop; nop; call " #func "; int3;"); \ 48 | extern void name(void); 49 | 50 | // x86_64: code to set stack pointer and jump to address 51 | #define FIX_SP_JMP(stack, addr) \ 52 | asm volatile("xchg %%rsp, %0; jmp *%%rax;" : "=r"(stack) : "0"(stack), "a"(addr) ); 53 | 54 | #elif __arm__ 55 | #define BITS32 56 | 57 | // arm: code to call a function with stack pointer as argument 58 | #define CALL_SP(addr) \ 59 | asm volatile("mov r0, sp; mov pc, %0;" : : "r"(addr) ); 60 | 61 | // arm: entry point of code injected into remote process 62 | // .inst 0xe7f001f0 = bkpt 63 | #define INJ_ENTRY(name, func) \ 64 | asm(".text; .global " #name "; " #name ":; nop; bl " #func ";.inst 0xe7f001f0;"); \ 65 | extern void name(void); 66 | 67 | // arm: code to set stack pointer and jump to address 68 | #define FIX_SP_JMP(stack, addrp) \ 69 | asm volatile("mov %%sp, %0; bx %[addr];" : "=r"(stack) : "0"(stack), [addr] "r"(addrp) ); 70 | 71 | #elif __aarch64__ 72 | #define BITS64 73 | 74 | // arm64: code to call a function with stack pointer as argument 75 | #define CALL_SP(func) \ 76 | asm volatile("mov x0, sp; bl " #func"; "); 77 | 78 | // arm64: entry point of code injected into remote process 79 | #define INJ_ENTRY(name, func) \ 80 | asm(".text; .global " #name "; " #name ":; nop; bl " #func ";.inst 0xd4200000;"); \ 81 | extern void name(void); 82 | 83 | // arm64: code to set stack pointer and jump to address 84 | #define FIX_SP_JMP(stackp, addrp) \ 85 | asm volatile("mov x0, sp; mov x1, %[stack]; sub x0, x1, x0; add sp, sp, x0; br %[addr]" \ 86 | : : [stack] "r"(stackp), [addr] "r"(addrp) : "x0", "x1"); 87 | 88 | #endif 89 | 90 | // small defines for auto detect injection address 91 | #ifdef BITS32 92 | #define MAPS_ADDR_MASK 0xf0000000 93 | #define MAPS_ADDR_ALIGN(x) (x + 0x00010000 - (x % 0x00010000)) 94 | #else 95 | #define MAPS_ADDR_MASK 0x7f0000000000 96 | #define MAPS_ADDR_ALIGN(x) (x + 0x01000000 - (x % 0x01000000)) 97 | #endif 98 | 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /icrt/icrt_mem.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: inline c runtime library // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // ixty malloc 8 | // use mmap for every required block 9 | // block header = [unsigned long user_size] [unsigned long allocd_size] 10 | 11 | // some defines 12 | #define IXTY_PAGE_SIZE 0x1000 13 | #define IXTY_SIZE_USER(ptr) (*(unsigned long *) ((unsigned long)(ptr) - 2 * sizeof(unsigned long))) 14 | #define IXTY_SIZE_ALLOC(ptr) (*(unsigned long *) ((unsigned long)(ptr) - 1 * sizeof(unsigned long))) 15 | #define IXTY_SIZE_HDR (2 * sizeof(unsigned long)) 16 | 17 | // this realloc will use the current mapped page if it is big enough 18 | void * realloc(void * addr, size_t size) 19 | { 20 | size_t alloc_size; 21 | unsigned long mem; 22 | 23 | if(!size) 24 | return NULL; 25 | 26 | if(addr && size < 0x1000 - IXTY_SIZE_HDR) 27 | { 28 | IXTY_SIZE_USER(addr) = size; 29 | return addr; 30 | } 31 | 32 | alloc_size = size + IXTY_SIZE_HDR; 33 | if(alloc_size % IXTY_PAGE_SIZE) 34 | alloc_size = ((alloc_size / IXTY_PAGE_SIZE) + 1) * IXTY_PAGE_SIZE; 35 | 36 | mem = (unsigned long)_mmap(NULL, alloc_size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 37 | if(mem < 0) 38 | { 39 | printf("> memory allocation error (0x%x bytes)\n", alloc_size); 40 | return NULL; 41 | } 42 | 43 | mem += IXTY_SIZE_HDR; 44 | IXTY_SIZE_USER(mem) = size; 45 | IXTY_SIZE_ALLOC(mem) = alloc_size; 46 | 47 | if(addr && IXTY_SIZE_USER(addr)) 48 | memcpy((void*)mem, addr, IXTY_SIZE_USER(addr)); 49 | if(addr) 50 | free(addr); 51 | 52 | return (void*)mem; 53 | } 54 | 55 | // wrapper around realloc 56 | void * malloc(size_t len) 57 | { 58 | return realloc(NULL, len); 59 | } 60 | 61 | // free mmapped page 62 | void free(void * ptr) 63 | { 64 | char * page = (char *)(ptr) - IXTY_SIZE_HDR; 65 | 66 | if(!ptr) 67 | return; 68 | 69 | _munmap(page, IXTY_SIZE_ALLOC(ptr)); 70 | } 71 | 72 | -------------------------------------------------------------------------------- /icrt/icrt_std.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: inline c runtime library // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // standard utilities (mem*, str*, ...) 8 | #ifndef _ICRT_STD_H 9 | #define _ICRT_STD_H 10 | 11 | void memset(void * dst, unsigned char c, unsigned int len) 12 | { 13 | unsigned char * p = (unsigned char *) dst; 14 | 15 | while(len--) 16 | *p++ = c; 17 | } 18 | 19 | int memcmp(void * dst, void * src, unsigned int len) 20 | { 21 | unsigned char * d = (unsigned char *) dst; 22 | unsigned char * s = (unsigned char *) src; 23 | 24 | while(len-- > 0) 25 | if(*d++ != *s++) 26 | return 1; 27 | 28 | return 0; 29 | } 30 | 31 | void memcpy(void *dst, void *src, unsigned int len) 32 | { 33 | unsigned char * d = (unsigned char *) dst; 34 | unsigned char * s = (unsigned char *) src; 35 | 36 | while(len--) 37 | *d++ = *s++; 38 | } 39 | 40 | void * memmem(void * haystack, size_t n, void * needle, size_t m) 41 | { 42 | for(int i=0; i maxlen ? maxlen : l; 60 | } 61 | 62 | int strncmp(char * s1, char * s2, size_t l) 63 | { 64 | for(size_t i=0; i> 1)) 101 | #endif 102 | #ifndef LONG_MIN 103 | #define LONG_MIN ((long)(~LONG_MAX)) 104 | #endif 105 | 106 | #define ISSPACE(c) (c == ' ' || c == '\r' || c == '\t' || c == '\n') 107 | #define ISDIGIT(c) (c >= '0' && c <= '9') 108 | #define ISALPHA(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) 109 | #define ISUPPER(c) (c >= 'A' && c <= 'Z') 110 | 111 | unsigned long strtoul(const char * nptr, char ** endptr, int base) 112 | { 113 | const char *s = nptr; 114 | unsigned long acc; 115 | unsigned long cutoff; 116 | int neg = 0, any, cutlim, c; 117 | 118 | do 119 | { 120 | c = *s++; 121 | } 122 | while (ISSPACE(c)); 123 | 124 | if (c == '-') 125 | { 126 | neg = 1; 127 | c = *s++; 128 | } 129 | else if (c == '+') 130 | c = *s++; 131 | if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) 132 | { 133 | c = s[1]; 134 | s += 2; 135 | base = 16; 136 | } 137 | if (base == 0) 138 | base = c == '0' ? 8 : 10; 139 | cutoff = (unsigned long)ULONG_MAX / (unsigned long)base; 140 | cutlim = (unsigned long)ULONG_MAX % (unsigned long)base; 141 | for (acc = 0, any = 0;; c = *s++) 142 | { 143 | if (ISDIGIT(c)) 144 | c -= '0'; 145 | else if (ISALPHA(c)) 146 | c -= ISUPPER(c) ? 'A' - 10 : 'a' - 10; 147 | else 148 | break; 149 | if (c >= base) 150 | break; 151 | if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) 152 | any = -1; 153 | else 154 | { 155 | any = 1; 156 | acc *= base; 157 | acc += c; 158 | } 159 | } 160 | if (any < 0) 161 | acc = ULONG_MAX; 162 | else if (neg) 163 | acc = -acc; 164 | if (endptr != 0) 165 | *endptr = (char *) (any ? s - 1 : nptr); 166 | return (acc); 167 | } 168 | 169 | int fmt_num(char * buf, size_t len, unsigned long val, int base) 170 | { 171 | unsigned long v = val; 172 | size_t n = 0; 173 | 174 | memset(buf, 0, len); 175 | 176 | // validate base first 177 | if(base != 0 && base != 8 && base != 10 && base != 16) 178 | return -1; 179 | if(base == 0) 180 | base = 10; 181 | 182 | // count required number of chars 183 | while(v) 184 | { 185 | v /= base; 186 | n += 1; 187 | } 188 | if(!n) 189 | n = 1; 190 | 191 | // check that we have enought room for string + null byte 192 | if(len < n + 1) 193 | return -1; 194 | 195 | for(int i=n-1; i>=0; i--) 196 | { 197 | if(base != 16 || val % 16 < 10) 198 | buf[i] = '0' + (val % base); 199 | else 200 | buf[i] = 'a' + ((val % 16) - 10); 201 | 202 | val /= base; 203 | } 204 | return 0; 205 | } 206 | 207 | // ugly AF but i dont want to include snprintf which has tons of globals / code / etc. 208 | // only supports %x %d %s 209 | // some modifiers are accepted but arent taken into account 210 | void printf(char * str, ...) 211 | { 212 | char tmp[32]; 213 | char * s = str; 214 | char * p = str; 215 | va_list args; 216 | va_start(args, str); 217 | 218 | while(1) 219 | { 220 | // end of string reached, output everything left to display 221 | if(!*p) 222 | { 223 | if(p - s > 0) 224 | _write(1, s, p - s); 225 | goto exit; 226 | } 227 | 228 | // format string? 229 | if(*p == '%' && *(p+1) != '%') 230 | { 231 | // output current string 232 | _write(1, s, p - s); 233 | 234 | // handle (and ignore :)) format string modifiers 235 | p += 1; 236 | while(ISDIGIT(*p) || *p == '.') 237 | p++; 238 | while(*p == 'l' || *p == 'h' || *p == 'b') 239 | p++; 240 | 241 | // we ignore sign aswell, unsigned only :) 242 | if(*p == 'i' || *p == 'I' || *p == 'd' || *p == 'D') 243 | { 244 | fmt_num(tmp, sizeof(tmp), va_arg(args, unsigned long), 10); 245 | _write(1, tmp, strlen(tmp)); 246 | } 247 | else if(*p == 'x' || *p == 'X') 248 | { 249 | fmt_num(tmp, sizeof(tmp), va_arg(args, unsigned long), 16); 250 | _write(1, tmp, strlen(tmp)); 251 | } 252 | else if(*p == 's') 253 | { 254 | char * fstr = va_arg(args, char *); 255 | _write(1, fstr, strlen(fstr)); 256 | } 257 | else 258 | _write(1, "", 3); 259 | 260 | p++; 261 | s = p; 262 | } 263 | // nope, skip 264 | else if(*p == '%') 265 | p++; 266 | 267 | p++; 268 | } 269 | 270 | exit: 271 | va_end(args); 272 | return; 273 | } 274 | 275 | #endif 276 | -------------------------------------------------------------------------------- /icrt/icrt_syscall.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: inline c runtime library // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // inline assembly implementation of linux syscalls 8 | // we define a _syscall_do() macro which calls a syscall with up to 6 arguments 9 | // arguments are assumed to be declared as a1, a2, ..., a6 10 | 11 | // we then use this macro to define macros for syscalls with specifid number of args 12 | // finally, we use those macros to define the functions that are of interest to us 13 | 14 | #ifndef _ICRT_SYSCALL_H 15 | #define _ICRT_SYSCALL_H 16 | 17 | // syscall numbers 18 | #include 19 | 20 | 21 | // ========================================================================== // 22 | // define syscall asm stub for all archs here 23 | // ========================================================================== // 24 | 25 | // x86 26 | #ifdef __i386__ 27 | #define _syscall_do(sys_nbr, rettype) \ 28 | { \ 29 | rettype ret = 0; \ 30 | register int r0 asm ("ebx") = (int)a1; \ 31 | register int r1 asm ("ecx") = (int)a2; \ 32 | register int r2 asm ("edx") = (int)a3; \ 33 | register int r3 asm ("esi") = (int)a4; \ 34 | register int r4 asm ("edi") = (int)a5; \ 35 | register int r5 asm ("ebp") = (int)a6; \ 36 | register int r7 asm ("eax") = sys_nbr; \ 37 | asm volatile \ 38 | ( \ 39 | "int $0x80;" \ 40 | : "=r" (ret) \ 41 | : "r"(r7), "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5) \ 42 | ); \ 43 | return ret; \ 44 | } 45 | 46 | // x86_64 aka amd64 47 | #elif __x86_64__ 48 | #define _syscall_do(sys_nbr, rettype) \ 49 | { \ 50 | register long r10 asm("r10") = (long)a4; \ 51 | register long r8 asm("r8") = (long)a5; \ 52 | register long r9 asm("r9") = (long)a6; \ 53 | rettype ret = 0; \ 54 | asm volatile \ 55 | ( \ 56 | "syscall" \ 57 | : "=a" (ret) \ 58 | : "0"(sys_nbr), "D"(a1), "S"(a2), \ 59 | "d"(a3), "r"(r10), "r"(r8), "r"(r9) \ 60 | : "cc", "rcx", "r11", "memory" \ 61 | ); \ 62 | return ret; \ 63 | } 64 | 65 | 66 | // arm 67 | #elif __arm__ 68 | #define _syscall_do(sys_nbr, rettype) \ 69 | { \ 70 | rettype ret = 0; \ 71 | register int r0 asm ("r0") = (int)a1; \ 72 | register int r1 asm ("r1") = (int)a2; \ 73 | register int r2 asm ("r2") = (int)a3; \ 74 | register int r3 asm ("r3") = (int)a4; \ 75 | register int r4 asm ("r4") = (int)a5; \ 76 | register int r5 asm ("r5") = (int)a6; \ 77 | register int r7 asm ("r7") = sys_nbr; \ 78 | asm volatile \ 79 | ( \ 80 | "swi #0; mov %0, r0" \ 81 | : "=r" (ret) \ 82 | : "r"(r7), "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5) \ 83 | ); \ 84 | return ret; \ 85 | } 86 | 87 | 88 | // arm64 89 | #elif __aarch64__ 90 | #define _syscall_do(sys_nbr, rettype) \ 91 | { \ 92 | rettype ret = 0; \ 93 | register long x0 asm ("x0") = (long)a1; \ 94 | register long x1 asm ("x1") = (long)a2; \ 95 | register long x2 asm ("x2") = (long)a3; \ 96 | register long x3 asm ("x3") = (long)a4; \ 97 | register long x4 asm ("x4") = (long)a5; \ 98 | register long x5 asm ("x5") = (long)a6; \ 99 | register long x8 asm ("x8") = sys_nbr; \ 100 | asm volatile \ 101 | ( \ 102 | "svc #0; mov %0, x0" \ 103 | : "=r" (ret) \ 104 | : "r"(x8), "r"(x0), "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5) \ 105 | ); \ 106 | return ret; \ 107 | } 108 | 109 | // something else? 110 | #else 111 | #error "unknown arch" 112 | #endif 113 | 114 | 115 | // ========================================================================== // 116 | // defines to generate syscall wrappers 117 | // ========================================================================== // 118 | 119 | #define _syscall6(sys_nbr, sys_name, rettype, t1, t2, t3, t4, t5, t6) \ 120 | static inline rettype sys_name(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5, t6 a6) \ 121 | { \ 122 | _syscall_do(sys_nbr, rettype) \ 123 | } 124 | 125 | #define _syscall5(sys_nbr, sys_name, rettype, t1, t2, t3, t4, t5) \ 126 | static inline rettype sys_name(t1 a1, t2 a2, t3 a3, t4 a4, t5 a5) \ 127 | { \ 128 | long a6=0; \ 129 | _syscall_do(sys_nbr, rettype) \ 130 | } 131 | 132 | #define _syscall4(sys_nbr, sys_name, rettype, t1, t2, t3, t4) \ 133 | static inline rettype sys_name(t1 a1, t2 a2, t3 a3, t4 a4) \ 134 | { \ 135 | long a6=0, a5=0; \ 136 | _syscall_do(sys_nbr, rettype) \ 137 | } 138 | 139 | #define _syscall3(sys_nbr, sys_name, rettype, t1, t2, t3) \ 140 | static inline rettype sys_name(t1 a1, t2 a2, t3 a3) \ 141 | { \ 142 | long a6=0, a5=0, a4=0; \ 143 | _syscall_do(sys_nbr, rettype) \ 144 | } 145 | 146 | #define _syscall2(sys_nbr, sys_name, rettype, t1, t2) \ 147 | static inline rettype sys_name(t1 a1, t2 a2) \ 148 | { \ 149 | long a6=0, a5=0, a4=0, a3=0; \ 150 | _syscall_do(sys_nbr, rettype) \ 151 | } 152 | 153 | #define _syscall1(sys_nbr, sys_name, rettype, t1) \ 154 | static inline rettype sys_name(t1 a1) \ 155 | { \ 156 | long a6=0, a5=0, a4=0, a3=0, a2=0; \ 157 | _syscall_do(sys_nbr, rettype) \ 158 | } 159 | 160 | #define _syscall0(sys_nbr, sys_name, rettype) \ 161 | static inline rettype sys_name(void) \ 162 | { \ 163 | long a6=0, a5=0, a4=0, a3=0, a2=0, a1=0; \ 164 | _syscall_do(sys_nbr, rettype) \ 165 | } 166 | 167 | 168 | // ========================================================================== // 169 | // define desired syscalls 170 | // ========================================================================== // 171 | 172 | _syscall0(SYS_getpid, _getpid, int) 173 | 174 | _syscall1(SYS_exit, __exit, int, int) 175 | void _exit(int c) { __exit(c); } 176 | 177 | _syscall1(SYS_close, _close, int, int) 178 | _syscall1(SYS_brk, _brk, long, unsigned long) 179 | 180 | _syscall2(SYS_munmap, _munmap, long, char*, int) 181 | 182 | _syscall3(SYS_read, _read, ssize_t, int, void *, size_t) 183 | _syscall3(SYS_write, _write, ssize_t, int, const void *, size_t) 184 | _syscall3(SYS_lseek, _lseek, long, int, long, int) 185 | _syscall3(SYS_mprotect, _mprotect, long, void*, long, int) 186 | 187 | #if __i386__ || __arm__ || __x86_64__ 188 | _syscall3(SYS_open, _open, int, char *, int, int) 189 | #else 190 | _syscall4(SYS_openat, _openat, int, int, char *, int, int) 191 | #define AT_FDCWD -100 192 | #define _open(a, b, c) _openat(AT_FDCWD, a, b, c) 193 | #endif 194 | 195 | _syscall4(SYS_ptrace, _ptrace, long, int, int, void*, void*) 196 | _syscall4(SYS_wait4, _wait4, int, int, int*, int, void*) 197 | 198 | #if __i386__ || __arm__ 199 | _syscall6(SYS_mmap2, _mmap, void *, void *, long, int, int, int, long) 200 | #else 201 | _syscall6(SYS_mmap, _mmap, void *, void *, long, int, int, int, long) 202 | #endif 203 | 204 | 205 | // ========================================================================== // 206 | // usefull constants for common syscalls 207 | // ========================================================================== // 208 | #define O_RDONLY 00 209 | #define O_WRONLY 01 210 | #define O_RDWR 02 211 | #define O_CREAT 0100 212 | #define O_TRUNC 01000 213 | #define O_APPEND 02000 214 | 215 | #define F_DUPFD 0 216 | #define F_GETFD 1 217 | #define F_SETFD 2 218 | #define F_GETFL 3 219 | #define F_SETFL 4 220 | 221 | #define SEEK_SET 0 222 | #define SEEK_CUR 1 223 | #define SEEK_END 2 224 | 225 | #define PROT_READ 0x1 226 | #define PROT_WRITE 0x2 227 | #define PROT_EXEC 0x4 228 | #define PROT_NONE 0x0 229 | 230 | #define MAP_SHARED 0x01 231 | #define MAP_PRIVATE 0x02 232 | #define MAP_TYPE 0x0f 233 | #define MAP_FIXED 0x10 234 | #define MAP_ANONYMOUS 0x20 235 | 236 | 237 | #define PTRACE_TRACEME 0 238 | #define PTRACE_PEEKTEXT 1 239 | #define PTRACE_PEEKDATA 2 240 | #define PTRACE_PEEKUSER 3 241 | #define PTRACE_POKETEXT 4 242 | #define PTRACE_POKEDATA 5 243 | #define PTRACE_POKEUSER 6 244 | #define PTRACE_CONT 7 245 | #define PTRACE_KILL 8 246 | #define PTRACE_SINGLESTEP 9 247 | #define PTRACE_GETREGS 12 248 | #define PTRACE_SETREGS 13 249 | #define PTRACE_GETFPREGS 14 250 | #define PTRACE_SETFPREGS 15 251 | #define PTRACE_ATTACH 16 252 | #define PTRACE_DETACH 17 253 | #define PTRACE_GETFPXREGS 18 254 | #define PTRACE_SETFPXREGS 19 255 | #define PTRACE_SET_SYSCALL 23 256 | #define PTRACE_SYSCALL 24 257 | #define PTRACE_SYSEMU 31 258 | 259 | #define PTRACE_GETREGSET 0x4204 260 | #define PTRACE_SETREGSET 0x4205 261 | 262 | #define NT_ARM_SYSTEM_CALL 0x404 263 | 264 | #define NT_PRSTATUS 1 265 | #define NT_PRFPREG 2 266 | #define NT_PRPSINFO 3 267 | #define NT_TASKSTRUCT 4 268 | #define NT_AUXV 6 269 | 270 | #endif 271 | -------------------------------------------------------------------------------- /icrt/icrt_utils.h: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: inline c runtime library // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // utilities (read_file, read /proc/pid/maps, ...) 8 | 9 | #ifndef _ICRT_UTILS_H 10 | #define _ICRT_UTILS_H 11 | 12 | // get file size, supports fds with no seek_end (such as /proc/pid/maps apparently...) 13 | long _get_file_size(int fd) 14 | { 15 | long fs, r; 16 | char tmp[0x1000]; 17 | 18 | // can seek to end? 19 | fs = _lseek(fd, 0, SEEK_END); 20 | if(fs > 0) 21 | { 22 | _lseek(fd, 0, SEEK_SET); 23 | return fs; 24 | } 25 | 26 | fs = 0; 27 | do 28 | { 29 | r = _read(fd, tmp, 0x1000); 30 | if(r < 0) 31 | return -1; 32 | else 33 | fs += r; 34 | } while(r > 0); 35 | 36 | _lseek(fd, 0, SEEK_SET); 37 | return fs; 38 | } 39 | 40 | // allocate memory + read a file 41 | int read_file(char * path, uint8_t ** out_buf, size_t * out_len) 42 | { 43 | long fs, r, n=0; 44 | int fd; 45 | 46 | *out_buf = 0; 47 | *out_len = 0; 48 | 49 | // open file 50 | if((fd = _open(path, O_RDONLY, 0)) < 0) 51 | return -1; 52 | 53 | // get file size 54 | if(!(fs = _get_file_size(fd))) 55 | return -1; 56 | *out_len = fs; 57 | 58 | // allocate mem 59 | if((*out_buf = realloc(0, fs)) == NULL) 60 | goto exit; 61 | 62 | // read file 63 | while(n < fs) 64 | { 65 | r = _read(fd, *out_buf + n, fs); 66 | if(r < 0) 67 | goto exit; 68 | n += r; 69 | if(r == 0 && n != fs) 70 | goto exit; 71 | } 72 | _close(fd); 73 | return 0; 74 | 75 | exit: 76 | if(*out_buf) 77 | { 78 | free(*out_buf); 79 | *out_buf = 0; 80 | } 81 | _close(fd); 82 | return -1; 83 | } 84 | 85 | int get_memmaps(int pid, uint8_t ** maps_buf, size_t * maps_len) 86 | { 87 | char path[256]; 88 | char pids[24]; 89 | 90 | // convert pid to string 91 | if(fmt_num(pids, sizeof(pids), pid, 10) < 0) 92 | return -1; 93 | 94 | // build /proc/pid/maps string 95 | memset(path, 0, sizeof(path)); 96 | strlcat(path, "/proc/", sizeof(path)); 97 | strlcat(path, pids, sizeof(path)); 98 | strlcat(path, "/maps", sizeof(path)); 99 | 100 | // read file 101 | if(read_file(path, maps_buf, maps_len) < 0) 102 | return -1; 103 | 104 | return 0; 105 | } 106 | 107 | // returns the address & size of the first section that matches _filter_ 108 | int get_section(int pid, char * filter, unsigned long * sec_start, size_t * sec_size) 109 | { 110 | uint8_t * maps_buf = NULL; 111 | size_t maps_len = 0; 112 | size_t off = 0; 113 | size_t min_len = *sec_size; 114 | char * p; 115 | 116 | // read /proc/pid/maps 117 | if(get_memmaps(pid, &maps_buf, &maps_len) < 0) 118 | return -1; 119 | 120 | // find first line that matches _filter_ 121 | do 122 | { 123 | if(!(p = memmem(maps_buf + off, maps_len - off, filter, strlen(filter)))) 124 | goto fail; 125 | off = (size_t)p - (size_t)maps_buf + 1; 126 | 127 | while(*p && p >= (char*)maps_buf && *p != '\n') 128 | p--; 129 | p++; 130 | 131 | // extract section addr & size 132 | *sec_start = strtoul(p, &p, 16); p++; 133 | *sec_size = strtoul(p, &p, 16) - (size_t)*sec_start; 134 | 135 | } while(*sec_size < min_len); 136 | 137 | 138 | free(maps_buf); 139 | return 0; 140 | 141 | fail: 142 | free(maps_buf); 143 | return -1; 144 | } 145 | 146 | // returns max - low address (doesnt start with F or 7F) used by the process 147 | unsigned long get_mapmax(int pid) 148 | { 149 | char * maps_buf = NULL; 150 | size_t maps_len = 0; 151 | char * p; 152 | unsigned long max = 0; 153 | 154 | // read /proc/pid/maps 155 | if(get_memmaps(pid, (uint8_t**)&maps_buf, &maps_len) < 0) 156 | return -1; 157 | 158 | // parse all maps 159 | for(p = (char*)maps_buf; p >= maps_buf && p < maps_buf + maps_len; ) 160 | { 161 | char * endline = memmem(p, maps_len - (p - maps_buf), "\n", 1); 162 | char * tmp = NULL; 163 | unsigned long e; 164 | unsigned long t; 165 | 166 | strtoul(p, &tmp, 16); 167 | if(!tmp) 168 | continue; 169 | tmp ++; 170 | e = strtoul(tmp, NULL, 16); 171 | t = e; 172 | 173 | while(t > 0xff) 174 | t >>= 8; 175 | if(t != 0xff && t != 0x7f) 176 | max = e; 177 | 178 | p = endline + 1; 179 | } 180 | free(maps_buf); 181 | printf("> auto-detected manual mapping address 0x%lx\n", MAPS_ADDR_ALIGN(max)); 182 | return MAPS_ADDR_ALIGN(max); 183 | } 184 | 185 | #endif 186 | -------------------------------------------------------------------------------- /mandibule.c: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: mandibule // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // only c file of our code injector 8 | // it includes directly all other code to be able to wrap all generated code 9 | // between start/end boundaries 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // first function in generated code - returns its own address aligned to page size 17 | unsigned long mandibule_beg(int aligned) 18 | { 19 | if(!aligned) 20 | return (unsigned long)mandibule_beg; 21 | return (unsigned long)mandibule_beg - ((unsigned long)mandibule_beg % 0x1000); 22 | } 23 | 24 | // include minimal inline c runtime + support code 25 | #include "icrt.h" 26 | #include "elfload.h" 27 | #include "fakestack.h" 28 | #include "ptinject.h" 29 | #include "shargs.h" 30 | 31 | // forward declarations 32 | unsigned long mandibule_end(void); 33 | void _main(unsigned long * sp); 34 | void payload_loadelf(ashared_t * args); 35 | 36 | // small macro for print + exit 37 | #define error(...) do { printf(__VA_ARGS__); _exit(1); } while(0) 38 | 39 | // define injected code entry point - which calls payload_main() 40 | INJ_ENTRY(payload_start, payload_main) 41 | 42 | // define injector entry point - which calls main() 43 | void _start(void) 44 | { 45 | CALL_SP(_main); 46 | } 47 | 48 | // show program usage & exit 49 | void usage(char * argv0, char * msg) 50 | { 51 | if(msg) 52 | printf("error: %s\n\n", msg); 53 | 54 | printf("usage: %s [-a arg]* [-e env]* [-m addr] \n", argv0); 55 | printf("\n"); 56 | printf("loads an ELF binary into a remote process.\n"); 57 | printf("\n"); 58 | printf("arguments:\n"); 59 | printf(" - elf: path of binary to inject into \n"); 60 | printf(" - pid: pid of process to inject into\n"); 61 | printf("\n"); 62 | printf("options:\n"); 63 | printf(" -a arg: argument to send to injected program - can be repeated\n"); 64 | printf(" -e env: environment value sent to injected program - can be repeated\n"); 65 | printf(" -m mem: base address at which program is loaded in remote process, default=AUTO\n"); 66 | printf("\n"); 67 | printf("Note: order of arguments must be respected (no getopt sry)\n"); 68 | printf("\n"); 69 | _exit(1); 70 | } 71 | 72 | // injector main code 73 | void _main(unsigned long * sp) 74 | { 75 | // argument parsing stuff 76 | int ac = *sp; 77 | char ** av = (char **)(sp + 1); 78 | ashared_t * args = NULL; 79 | 80 | // injection vars 81 | void * inj_addr = (void*)mandibule_beg(1); 82 | size_t inj_size = mandibule_end() - (unsigned long)inj_addr; 83 | size_t inj_off = (size_t)payload_start - (size_t)inj_addr; 84 | size_t inj_opts = mandibule_beg(0) - mandibule_beg(1); 85 | uint8_t * inj_code = malloc(inj_size); 86 | 87 | // parse arguments & build shared arguments struct 88 | args = _ashared_parse(ac, av); 89 | if(inj_opts < args->size_used) 90 | error("> shared arguments too big (%d/ max %d)\n", args->size_max, inj_opts); 91 | 92 | // prepare code that will be injected into the process 93 | if(!inj_code) 94 | error("> malloc for injected code failed\n"); 95 | memcpy(inj_code, inj_addr, inj_size); 96 | memcpy(inj_code, args, args->size_used); 97 | 98 | // self injection test 99 | if(args->pid == 0) 100 | { 101 | args->pid = _getpid(); 102 | printf("> self inject pid: %d - bypassing ptrace altogether\n", args->pid); 103 | payload_loadelf(args); 104 | } 105 | else 106 | { 107 | // inject our own code into & execute code at 108 | if(pt_inject(args->pid, inj_code, inj_size, inj_off) < 0) 109 | error("> failed to inject shellcode into pid %d\n", args->pid); 110 | 111 | printf("> successfully injected shellcode into pid %d\n", args->pid); 112 | } 113 | _exit(0); 114 | } 115 | 116 | void payload_loadelf(ashared_t * args) 117 | { 118 | char pids[24]; 119 | char path[256]; 120 | uint8_t * auxv_buf; 121 | size_t auxv_len; 122 | char ** av; 123 | char ** env; 124 | uint8_t fakestack[4096 * 16]; 125 | uint8_t * stackptr = fakestack + sizeof(fakestack); 126 | unsigned long eop; 127 | unsigned long base_addr; 128 | 129 | // convert pid to string 130 | if(fmt_num(pids, sizeof(pids), args->pid, 10) < 0) 131 | return; 132 | 133 | // read auxv 134 | memset(path, 0, sizeof(path)); 135 | strlcat(path, "/proc/", sizeof(path)); 136 | strlcat(path, pids, sizeof(path)); 137 | strlcat(path, "/auxv", sizeof(path)); 138 | if(read_file(path, &auxv_buf, &auxv_len) < 0) 139 | return; 140 | printf("> auxv len: %d\n", auxv_len); 141 | 142 | // build argv from args 143 | av = malloc((args->count_arg + 1) * sizeof(char*)); 144 | memset(av, 0, (args->count_arg + 1) * sizeof(char*)); 145 | for(int i=0; icount_arg; i++) 146 | av[i] = _ashared_get(args, i, 1); 147 | 148 | // build envp from args 149 | env = malloc((args->count_env + 1) * sizeof(char*)); 150 | for(int i=0; icount_env; i++) 151 | env[i] = _ashared_get(args, i, 0); 152 | 153 | // autodetect binary mapping address? 154 | base_addr = args->base_addr == -1 ? get_mapmax(args->pid) : args->base_addr; 155 | printf("> mapping '%s' into memory at 0x%lx\n", av[0], base_addr); 156 | 157 | // load the elf into memory! 158 | if(map_elf(av[0], base_addr, (unsigned long *)auxv_buf, &eop) < 0) 159 | error("> failed to load elf\n"); 160 | 161 | // build a stack for loader entry 162 | memset(fakestack, 0, sizeof(fakestack)); 163 | stackptr = fake_stack(stackptr, args->count_arg, av, env, (unsigned long *)auxv_buf); 164 | 165 | // all done 166 | printf("> starting ...\n\n"); 167 | FIX_SP_JMP(stackptr, eop); 168 | 169 | // never reached if everything goes well 170 | printf("> returned from loader\n"); 171 | free(auxv_buf); 172 | free(av); 173 | free(env); 174 | _exit(1); 175 | } 176 | 177 | // main function for injected code 178 | // executed in remote process 179 | void payload_main(void) 180 | { 181 | // get the arguments from memory 182 | // we overwrite the ELF header with the arguments for the injected copy of our code 183 | ashared_t * args = (ashared_t*)mandibule_beg(1); 184 | _ashared_print(args); 185 | 186 | // load elf into memory 187 | payload_loadelf(args); 188 | _exit(0); 189 | } 190 | 191 | // must be the last function in the .c file 192 | unsigned long mandibule_end(void) 193 | { 194 | uint8_t * p = (uint8_t*)"-= end_rodata =-"; 195 | p += 0x1000 - ((unsigned long)p % 0x1000); 196 | return (unsigned long)p; 197 | } 198 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # mandibule: linux elf injector 2 | 3 | ## intro 4 | Mandibule is a program that allows to inject an ELF file into a remote process. 5 | 6 | Both static & dynamically linked programs can be targetted. 7 | Supported archs: 8 | 9 | - x86 10 | - x86_64 11 | - arm 12 | - aarch64 13 | 14 | Example usage: https://asciinema.org/a/KkOHP2Jef0E6wViPCglkXLRcV 15 | 16 | @ixty 2018 17 | 18 | 19 | ## installation 20 | ```shell 21 | git clone https://github.com/ixty/mandibule 22 | make 23 | ``` 24 | 25 | 26 | ## usage 27 | ```shell 28 | usage: ./mandibule [-a arg]* [-e env]* [-m addr] 29 | 30 | loads an ELF binary into a remote process. 31 | 32 | arguments: 33 | - elf: path of binary to inject into 34 | - pid: pid of process to inject into 35 | 36 | options: 37 | -a arg: argument to send to injected program - can be repeated 38 | -e env: environment value sent to injected program - can be repeated 39 | -m mem: base address at which program is loaded in remote process, default=AUTO 40 | 41 | Note: order of arguments must be respected (no getopt sry) 42 | ``` 43 | 44 | 45 | ## example run 46 | ```shell 47 | $ make x86_64 48 | 49 | # in shell 1 50 | $ ./target 51 | > started. 52 | ...... 53 | 54 | # in shell 2 55 | $ ./mandibule ./toinject `pidof target` 56 | > target pid: 6266 57 | > arg[0]: ./toinject 58 | > args size: 51 59 | > shellcode injection addr: 0x7f0f4719c000 size: 0x5000 (available: 0x195000) 60 | > success attaching to pid 6266 61 | > backed up mem & registers 62 | > injected shellcode at 0x7f0f4719c000 63 | > running shellcode.. 64 | > shellcode executed! 65 | > restored memory & registers 66 | > successfully injected shellcode into pid 6266 67 | 68 | # back to shell 1 69 | ... 70 | > target pid: 6266 71 | > arg[0]: ./toinject 72 | > args size: 51 73 | > auxv len: 304 74 | > auto-detected manual mapping address 0x55f6e1000000 75 | > mapping './toinject' into memory at 0x55f6e1000000 76 | > reading elf file './toinject' 77 | > loading elf at: 0x55f6e1000000 78 | > load segment addr 0x55f6e1000000 len 0x1000 => 0x55f6e1000000 79 | > load segment addr 0x55f6e1200dd8 len 0x1000 => 0x55f6e1200000 80 | > max vaddr 0x55f6e1212000 81 | > loading interp '/lib64/ld-linux-x86-64.so.2' 82 | > reading elf file '/lib64/ld-linux-x86-64.so.2' 83 | > loading elf at: 0x55f6e1212000 84 | > load segment addr 0x55f6e1212000 len 0x23000 => 0x55f6e1212000 85 | > load segment addr 0x55f6e1435bc0 len 0x2000 => 0x55f6e1435000 86 | > max vaddr 0x55f6e1448000 87 | > eop 0x55f6e1212c20 88 | > setting auxv 89 | > set auxv[3] to 0x55f6e1000040 90 | > set auxv[5] to 0x9 91 | > set auxv[9] to 0x55f6e10006e0 92 | > set auxv[7] to 0x55f6e1000000 93 | > eop 0x55f6e1212c20 94 | > starting ... 95 | 96 | # oh hai from pid 6266 97 | # arg[0]: ./toinject 98 | # :) 99 | # :) 100 | # :) 101 | # bye! 102 | ........... 103 | 104 | 105 | ``` 106 | 107 | 108 | ## injection proces 109 | mandibule has no dependency (not even libc) and is compiled with pie and fpie in order to make it fully relocatable. 110 | 111 | This way we can copy mandibule's code into any process and it will be able to run as if it were a totally independant shellcode. 112 | 113 | Here is how mandibule works: 114 | 115 | - find an executable section in target process with enough space (~5Kb) 116 | - attach to process with ptrace 117 | - backup register state 118 | - backup executable section 119 | - inject mandibule code into executable section 120 | - let the execution resume on our own injected code 121 | - wait until exit() is called by the remote process 122 | - restore registers & memory 123 | - detach from process 124 | 125 | In the remote process, mandibule does the following: 126 | 127 | - read arguments, environment variables and other options from its own memory 128 | - find a suitable memory address to load the target elf file if needed 129 | - manually load & map the elf file into memory using only syscalls 130 | - load the ld-linux interpreter if needed 131 | - call the main function of the manually loaded binary 132 | 133 | 134 | ## tested on 135 | 136 | - __x86__: Linux debian 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u5 (2017-09-19) x86_64 GNU/Linux 137 | - __x86_64__: Linux debian 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u5 (2017-09-19) x86_64 GNU/Linux 138 | - __arm64__: Linux buildroot 4.13.6 #1 SMP Sat Mar 3 16:40:18 UTC 2018 aarch64 GNU/Linux 139 | - __arm__: Linux buildroot 4.11.3 #1 SMP Sun Mar 4 02:36:56 UTC 2018 armv7l GNU/Linux 140 | 141 | arm & arm64 where tested using [arm_now](https://github.com/nongiach/arm_now) by [@chaignc](https://twitter.com/chaignc) to easily spawn qemu vms with the desired arch. 142 | -------------------------------------------------------------------------------- /samples/target.c: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: mandibule // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // small example of a target program we inject code into 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | int main(int ac, char ** av, char ** env) 14 | { 15 | printf("> started.\n"); 16 | 17 | while(1) 18 | { 19 | printf("."); 20 | fflush(stdout); 21 | sleep(1); 22 | } 23 | 24 | printf("> done.\n"); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /samples/toinject.c: -------------------------------------------------------------------------------- 1 | // ======================================================================== // 2 | // author: ixty 2018 // 3 | // project: mandibule // 4 | // licence: beerware // 5 | // ======================================================================== // 6 | 7 | // small example of code that we inject into a remote process 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int main(int ac, char ** av, char ** env) 16 | { 17 | printf("# oh hai from pid %d\n", getpid()); 18 | for(int i=0; i