├── src ├── dummy.c ├── Makefile ├── count.asm ├── ptrace.h ├── syscall.h ├── syscall.c ├── ptrace.c └── inject.c ├── README.md ├── .gitignore └── LICENSE /src/dummy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, const char *argv[]) 6 | { 7 | char *hello = "Hello tracer!"; 8 | printf("hello @ %p: %s\n", hello, hello); 9 | 10 | while (1) { 11 | printf("."); 12 | fflush(stdout); 13 | sleep(1); 14 | } 15 | 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -lcapstone 3 | AS = nasm 4 | ASFLAGS = -f bin 5 | 6 | all: inject dummy count 7 | 8 | debug: CFLAGS += -DDEBUG -g 9 | debug: all 10 | 11 | %.o: %.c 12 | $(CC) $(CFLAGS) -o $@ -c $< 13 | 14 | inject: inject.o ptrace.o syscall.o 15 | $(CC) $^ $(CFLAGS) $(LDFLAGS) -o $@ 16 | 17 | dummy: dummy.o 18 | $(CC) $^ $(CFLAGS) $(LDFLAGS) -o $@ 19 | 20 | count: count.asm 21 | $(AS) $^ $(ASFLAGS) -o $@ 22 | #$(AS) $^ -f elf64 -o count.o 23 | #ld -m elf_x86_64 count.o -o count 24 | 25 | clean: 26 | -$(RM) *.o inject dummy count 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # procjack 2 | 3 | PoC of injecting code into a running Linux process. 4 | Spawns a new thread to host the injected code. 5 | 6 | ## Example 7 | 8 | This will inject shellcode, contained in the [`count`](src/count.asm) binary, into a running process 'dummy'. 9 | ``` 10 | $ cd src 11 | $ make 12 | $ ./dummy & 13 | $ ./inject -p $(pidof dummy) -f ./count 14 | ``` 15 | 16 | Quick-n-dirty command to verify that a process is running multiple threads 17 | ``` 18 | $ ps -eLF | head -1; ps -eLF | grep -i dummy 19 | ``` 20 | 21 | ## Dependencies 22 | 23 | * [Capstone](http://www.capstone-engine.org/documentation.html) (`apt-get install libcapstone3 libcapstone-dev`) 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Swap 2 | [._]*.s[a-v][a-z] 3 | [._]*.sw[a-p] 4 | [._]s[a-v][a-z] 5 | [._]sw[a-p] 6 | 7 | # Session 8 | Session.vim 9 | 10 | # Temporary 11 | .netrwhist 12 | *~ 13 | # Auto-generated tag files 14 | tags 15 | # Prerequisites 16 | *.d 17 | 18 | # Object files 19 | *.o 20 | *.ko 21 | *.obj 22 | *.elf 23 | 24 | # Linker output 25 | *.ilk 26 | *.map 27 | *.exp 28 | 29 | # Precompiled Headers 30 | *.gch 31 | *.pch 32 | 33 | # Libraries 34 | *.lib 35 | *.a 36 | *.la 37 | *.lo 38 | 39 | # Shared objects (inc. Windows DLLs) 40 | *.dll 41 | *.so 42 | *.so.* 43 | *.dylib 44 | 45 | # Executables 46 | *.exe 47 | *.out 48 | *.app 49 | *.i*86 50 | *.x86_64 51 | *.hex 52 | 53 | # Debug files 54 | *.dSYM/ 55 | *.su 56 | *.idb 57 | *.pdb 58 | 59 | # Kernel Module Compile Results 60 | *.mod* 61 | *.cmd 62 | .tmp_versions/ 63 | modules.order 64 | Module.symvers 65 | Mkfile.old 66 | .gdb_history 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Henrik Nårstad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/count.asm: -------------------------------------------------------------------------------- 1 | BITS 64 2 | section .text 3 | 4 | global _start 5 | _start: 6 | mov rbp, rsp 7 | sub rsp, 32 8 | mov byte [rbp - 16], '0' 9 | mov byte [rbp - 15], ' ' 10 | mov byte [rbp - 14], 0 11 | 12 | mov byte [rbp - 13], 0x0a 13 | mov byte [rbp - 12], 0 14 | 15 | loop: 16 | mov rsi, 3 17 | lea rdi, [rbp - 16] 18 | call write 19 | 20 | mov byte al, [rbp - 16] 21 | inc al 22 | cmp al, 10 + '0' 23 | jl nice 24 | 25 | ; write newline and reset counter 26 | mov rax, '0' 27 | push rax 28 | lea rdi, [rbp - 13] 29 | mov rsi, 1 30 | call write 31 | pop rax 32 | 33 | nice: 34 | mov byte [rbp - 16], al 35 | 36 | mov rdi, 1 37 | call nanosleep 38 | jmp loop 39 | 40 | 41 | write: 42 | mov rdx, rsi 43 | mov rsi, rdi 44 | mov rdi, 1 45 | mov rax, 1 46 | syscall 47 | ret 48 | 49 | nanosleep: 50 | push rbp 51 | mov rbp, rsp 52 | sub rsp, 32 53 | 54 | mov qword [rbp - 32], 0 55 | mov qword [rbp - 24], 0 56 | mov dword [rbp - 32], edi 57 | 58 | lea rdi, [rbp - 32] 59 | mov rsi, 0 60 | 61 | mov rax, 35 62 | syscall 63 | 64 | mov rsp, rbp 65 | pop rbp 66 | ret 67 | -------------------------------------------------------------------------------- /src/ptrace.h: -------------------------------------------------------------------------------- 1 | #ifndef __PTRACE_H 2 | #define __PTRACE_H 3 | 4 | #include 5 | #include 6 | 7 | struct ptrace_info; 8 | 9 | #define MEM_PERM_READ (1 << 2) 10 | #define MEM_PERM_WRITE (1 << 1) 11 | #define MEM_PERM_EXEC (1 << 0) 12 | 13 | #define mem_map_length(ents) *(((uint32_t *)ents) - 1) 14 | #define mem_map_foreach(ents, ptr) \ 15 | for ((ptr) = (ents); (ptr) < (ents) + mem_map_length(ents); ++(ptr)) 16 | 17 | struct mem_map_entry { 18 | void *addr; 19 | size_t size; 20 | uint8_t perms; 21 | char *pathname; 22 | }; 23 | 24 | extern struct ptrace_info *ptrace_attach(int pid); 25 | extern long ptrace_detach(struct ptrace_info *info); 26 | extern void ptrace_free(struct ptrace_info *info); 27 | extern int ptrace_readmem(struct ptrace_info *info, void *addr, void *buf, size_t n); 28 | extern int ptrace_writemem(struct ptrace_info *info, void *addr, void *src, size_t n); 29 | extern struct mem_map_entry *get_process_memory(struct ptrace_info *info); 30 | extern void mem_maps_free(struct mem_map_entry *ent); 31 | extern int ptrace_getregs(struct ptrace_info *info, struct user_regs_struct *regs); 32 | extern int ptrace_setregs(struct ptrace_info *info, struct user_regs_struct *regs); 33 | extern int ptrace_continue_no_block(struct ptrace_info *info); 34 | extern int ptrace_continue(struct ptrace_info *info); 35 | extern int ptrace_step(struct ptrace_info *info); 36 | extern int ptrace_step_n(struct ptrace_info *info, int n); 37 | extern int mommy_am_i_inside_a_SO(struct ptrace_info *info); 38 | 39 | #endif /* end of include guard: __PTRACE_H */ 40 | -------------------------------------------------------------------------------- /src/syscall.h: -------------------------------------------------------------------------------- 1 | #ifndef __SYSCALL_H 2 | #define __SYSCALL_H 3 | 4 | #define syscall1(out_buf, syscall_no, a) \ 5 | asm_syscall(1, out_buf, syscall_no, a) 6 | #define syscall2(out_buf, syscall_no, a, b) \ 7 | asm_syscall(2, out_buf, syscall_no, a, b) 8 | #define syscall3(out_buf, syscall_no, a, b, c) \ 9 | asm_syscall(3, out_buf, syscall_no, a, b, c) 10 | #define syscall4(out_buf, syscall_no, a, b, c, d) \ 11 | asm_syscall(4, out_buf, syscall_no, a, b, c, d) 12 | #define syscall5(out_buf, syscall_no, a, b, c, d, e) \ 13 | asm_syscall(5, out_buf, syscall_no, a, b, c, d, e) 14 | #define syscall6(out_buf, syscall_no, a, b, c, d, e, f) \ 15 | asm_syscall(6, out_buf, syscall_no, a, b, c, d, e, f) 16 | 17 | #define asm_mmap(out_buf, addr, len, prot, flags, fd, offset) \ 18 | syscall6(out_buf, 9, addr, len, prot, flags, fd, offset) 19 | #define asm_munmap(out_buf, addr, len) \ 20 | syscall2(out_buf, 11, addr, len) 21 | 22 | extern int int3(uint8_t *buf); 23 | extern int mov_rax(uint64_t num, uint8_t *buf); 24 | extern int mov_rdi(uint64_t num, uint8_t *buf); 25 | extern int mov_rsi(uint64_t num, uint8_t *buf); 26 | extern int mov_rdx(uint64_t num, uint8_t *buf); 27 | extern int mov_r10(uint64_t num, uint8_t *buf); 28 | extern int mov_r8(uint64_t num, uint8_t *buf); 29 | extern int mov_r9(uint64_t num, uint8_t *buf); 30 | extern int asm_syscall(int num_args, uint8_t *out_buf, int syscall_no, ...); 31 | extern int asm_clone(uint8_t *out_buf, uint64_t stack_addr, uint64_t thread_code); 32 | extern int disasm(uint8_t *code, int code_len, uint64_t start_addr, int num_instructions); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/syscall.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define min(x, y) ((x) < (y) ? (x) : (y)) 11 | 12 | int int3(uint8_t *buf) 13 | { 14 | uint8_t int3[] = { 0xcc }; 15 | memcpy(buf, int3, sizeof(int3)); 16 | return sizeof(int3); 17 | } 18 | 19 | int mov_rax(uint64_t num, uint8_t *buf) 20 | { 21 | uint8_t opc[] = { 0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 22 | uint64_t *val = (uint64_t *)&buf[2]; 23 | memcpy(buf, opc, sizeof(opc)); 24 | *val = num; 25 | return sizeof(opc); 26 | } 27 | 28 | int mov_rdi(uint64_t num, uint8_t *buf) 29 | { 30 | uint8_t opc[] = { 0x48, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 31 | uint64_t *val = (uint64_t *)&buf[2]; 32 | memcpy(buf, opc, sizeof(opc)); 33 | *val = num; 34 | return sizeof(opc); 35 | } 36 | 37 | int mov_rsi(uint64_t num, uint8_t *buf) 38 | { 39 | uint8_t opc[] = { 0x48, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 40 | uint64_t *val = (uint64_t *)&buf[2]; 41 | memcpy(buf, opc, sizeof(opc)); 42 | *val = num; 43 | return sizeof(opc); 44 | } 45 | 46 | int mov_rdx(uint64_t num, uint8_t *buf) 47 | { 48 | uint8_t opc[] = { 0x48, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 49 | uint64_t *val = (uint64_t *)&buf[2]; 50 | memcpy(buf, opc, sizeof(opc)); 51 | *val = num; 52 | return sizeof(opc); 53 | } 54 | 55 | int mov_r10(uint64_t num, uint8_t *buf) 56 | { 57 | uint8_t opc[] = { 0x49, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 58 | uint64_t *val = (uint64_t *)&buf[2]; 59 | memcpy(buf, opc, sizeof(opc)); 60 | *val = num; 61 | return sizeof(opc); 62 | } 63 | 64 | int mov_r8(uint64_t num, uint8_t *buf) 65 | { 66 | uint8_t opc[] = { 0x49, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 67 | uint64_t *val = (uint64_t *)&buf[2]; 68 | memcpy(buf, opc, sizeof(opc)); 69 | *val = num; 70 | return sizeof(opc); 71 | } 72 | 73 | int mov_r9(uint64_t num, uint8_t *buf) 74 | { 75 | uint8_t opc[] = { 0x49, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 76 | uint64_t *val = (uint64_t *)&buf[2]; 77 | memcpy(buf, opc, sizeof(opc)); 78 | *val = num; 79 | return sizeof(opc); 80 | } 81 | 82 | int asm_syscall(int num_args, uint8_t *out_buf, int syscall_no, ...) 83 | { 84 | va_list ap; 85 | int arg_no; 86 | uint64_t arg; 87 | uint8_t *bufptr = out_buf; 88 | uint8_t _syscall[] = { 0x0f, 0x05 }; 89 | 90 | int (*reg_init_func[])(uint64_t, uint8_t *) = { 91 | mov_rdi, 92 | mov_rsi, 93 | mov_rdx, 94 | mov_r10, 95 | mov_r8, 96 | mov_r9 97 | }; 98 | num_args = min(num_args, 6); 99 | 100 | va_start(ap, syscall_no); 101 | for (arg_no = 0; arg_no < num_args; ++arg_no) { 102 | arg = va_arg(ap, uint64_t); 103 | bufptr += reg_init_func[arg_no](arg, bufptr); 104 | } 105 | va_end(ap); 106 | 107 | bufptr += mov_rax(syscall_no, bufptr); 108 | memcpy(bufptr, _syscall, sizeof(_syscall)); 109 | bufptr += sizeof(_syscall); 110 | 111 | return (int)(bufptr - out_buf); 112 | } 113 | 114 | int asm_clone(uint8_t *out_buf, uint64_t stack_addr, uint64_t thread_code) 115 | { 116 | int syscall_len; 117 | uint8_t stub[] = { 118 | 0x48, 0x85, 0xc0, // test rax,rax 119 | 0x74, 0x01, // jz +1 (skip int3 for thread) 120 | //0x90, // nop 121 | 0xcc, // int3 122 | 0x48, 0xb8, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12, // mov rax, 0x123456781234578 123 | 0xff, 0xe0 // jmp rax 124 | }; 125 | uint64_t *jump_target = (uint64_t *)&stub[8]; 126 | 127 | *jump_target = thread_code; 128 | 129 | syscall_len = asm_syscall(5, out_buf, 56, CLONE_SIGHAND|CLONE_FS|CLONE_VM|CLONE_FILES|CLONE_THREAD, 130 | stack_addr, 0, 0, 0); 131 | 132 | memcpy(out_buf + syscall_len, stub, sizeof(stub)); 133 | return syscall_len + sizeof(stub); 134 | } 135 | 136 | int disasm(uint8_t *code, int code_len, uint64_t start_addr, int num_instructions) 137 | { 138 | csh handle; 139 | cs_insn *insn; 140 | size_t count, j; 141 | 142 | if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) 143 | return -1; 144 | 145 | count = cs_disasm(handle, code, code_len, start_addr, 0, &insn); 146 | if (count <= 0) { 147 | printf("ERROR: Failed to disassemble given code!\n"); 148 | cs_close(&handle); 149 | return 0; 150 | } 151 | 152 | for (j = 0; j < min(count, num_instructions); j++) { 153 | printf("0x%lx:\t%s\t\t%s\n", insn[j].address, insn[j].mnemonic, 154 | insn[j].op_str); 155 | } 156 | 157 | cs_free(insn, count); 158 | 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /src/ptrace.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ptrace.h" 12 | 13 | struct ptrace_info { 14 | int pid; 15 | }; 16 | 17 | struct ptrace_info *ptrace_attach(int pid) 18 | { 19 | struct ptrace_info *ret; 20 | 21 | if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) 22 | return NULL; 23 | 24 | ret = calloc(1, sizeof(struct ptrace_info)); 25 | ret->pid = pid; 26 | 27 | waitpid(pid, NULL, 0); 28 | return ret; 29 | } 30 | 31 | long ptrace_detach(struct ptrace_info *info) 32 | { 33 | return ptrace(PTRACE_DETACH, info->pid); 34 | } 35 | 36 | void ptrace_free(struct ptrace_info *info) 37 | { 38 | free(info); 39 | } 40 | 41 | static uint32_t read_word(struct ptrace_info *info, void *addr) 42 | { 43 | uint32_t ret = ptrace(PTRACE_PEEKTEXT, info->pid, addr, NULL); 44 | if (ret == 0xffffffff && errno) { 45 | perror("peekdata"); 46 | } 47 | return ret; 48 | } 49 | 50 | int ptrace_readmem(struct ptrace_info *info, void *addr, void *buf, size_t n) 51 | { 52 | size_t i; 53 | uint32_t word; 54 | int wordsize = sizeof(word); 55 | uint64_t curaddr = (uint64_t)addr; 56 | uint8_t *bufptr = buf; 57 | 58 | for (i = 0; i + wordsize <= n; i += wordsize, curaddr += wordsize) { 59 | word = read_word(info, (void *)curaddr); 60 | memcpy(bufptr + i, &word, wordsize); 61 | } 62 | 63 | if (i < n) { 64 | word = read_word(info, (void *)curaddr); 65 | memcpy(bufptr + i, &word, n - i); 66 | } 67 | 68 | return (int)n; 69 | } 70 | 71 | static long write_word(struct ptrace_info *info, void *addr, uint32_t word) 72 | { 73 | return ptrace(PTRACE_POKETEXT, info->pid, addr, (void *)(uint64_t)word); 74 | } 75 | 76 | int ptrace_writemem(struct ptrace_info *info, void *addr, void *src, size_t n) 77 | { 78 | size_t i; 79 | uint32_t word; 80 | int wordsize = sizeof(word); 81 | uint64_t curaddr = (uint64_t)addr; 82 | uint8_t *srcptr = src; 83 | 84 | for (i = 0; i + wordsize <= n; i += wordsize, curaddr += wordsize, srcptr += wordsize) { 85 | if (write_word(info, (void *)curaddr, *((uint32_t *)srcptr)) == -1) 86 | return -1; 87 | } 88 | 89 | if (i < n) { 90 | word = read_word(info, (void *)curaddr); 91 | memcpy(&word, srcptr, n - i); 92 | if (write_word(info, (void *)curaddr, *((uint32_t *)srcptr)) == -1) 93 | return -1; 94 | } 95 | 96 | return (int)n; 97 | } 98 | 99 | static void parse_maps_ent(char *str, struct mem_map_entry *ent) 100 | { 101 | int i; 102 | uint64_t start, end; 103 | char *str2, *token, *saveptr, *tmp; 104 | 105 | for (i = 0, str2 = str; ; str2 = NULL, ++i) { 106 | token = strtok_r(str2, " ", &saveptr); 107 | if (!token) 108 | break; 109 | 110 | switch (i) { 111 | case 0: 112 | tmp = strchr(token, '-'); 113 | *tmp++ = 0; 114 | 115 | start = strtoul(token, NULL, 16); 116 | end = strtoul(tmp, NULL, 16); 117 | *--tmp = '-'; 118 | 119 | ent->addr = (void *)start; 120 | ent->size = end - start; 121 | break; 122 | case 1: 123 | if (token[0] == 'r') 124 | ent->perms |= MEM_PERM_READ; 125 | if (token[1] == 'w') 126 | ent->perms |= MEM_PERM_WRITE; 127 | if (token[2] == 'x') 128 | ent->perms |= MEM_PERM_EXEC; 129 | break; 130 | case 5: 131 | ent->pathname = strdup(token); 132 | break; 133 | } 134 | } 135 | } 136 | 137 | struct mem_map_entry *get_process_memory(struct ptrace_info *info) 138 | { 139 | char buf[1024]; 140 | uint32_t *num; 141 | int num_ents = 20, i; 142 | struct mem_map_entry *ret, *current; 143 | FILE *fp; 144 | 145 | sprintf(buf, "/proc/%d/maps", info->pid); 146 | fp = fopen(buf, "r"); 147 | if (!fp) 148 | return NULL; 149 | 150 | num = calloc(1, sizeof(struct mem_map_entry) * num_ents + sizeof(uint32_t)); 151 | ret = current = (struct mem_map_entry *)((uint32_t *)num + 1); 152 | 153 | for (i = 0; fgets(buf, sizeof(buf), fp); ++i, ++current) { 154 | if (i >= num_ents) { 155 | num_ents += 10; 156 | num = realloc(num, num_ents * sizeof(struct mem_map_entry) + sizeof(uint32_t)); 157 | ret = (struct mem_map_entry *)((uint32_t *)num + 1); 158 | current = ret + i; 159 | } 160 | 161 | if (strchr(buf, '\n')) 162 | *strchr(buf, '\n') = 0; 163 | parse_maps_ent(buf, current); 164 | } 165 | 166 | *num = i; 167 | return ret; 168 | } 169 | 170 | void mem_maps_free(struct mem_map_entry *ent) 171 | { 172 | uint32_t *num, i; 173 | num = ((uint32_t *)ent) - 1; 174 | 175 | for (i = 0; i < *num; i++) 176 | free(ent[i].pathname); 177 | free(num); 178 | } 179 | 180 | int ptrace_getregs(struct ptrace_info *info, struct user_regs_struct *regs) 181 | { 182 | return ptrace(PTRACE_GETREGS, info->pid, NULL, regs); 183 | } 184 | 185 | int ptrace_setregs(struct ptrace_info *info, struct user_regs_struct *regs) 186 | { 187 | return ptrace(PTRACE_SETREGS, info->pid, NULL, regs); 188 | } 189 | 190 | int ptrace_continue_no_block(struct ptrace_info *info) 191 | { 192 | if (ptrace(PTRACE_CONT, info->pid, NULL, NULL) == -1) 193 | return -1; 194 | return 0; 195 | } 196 | 197 | int ptrace_continue(struct ptrace_info *info) 198 | { 199 | if (ptrace_continue_no_block(info) == -1) 200 | return -1; 201 | return waitpid(info->pid, NULL, 0); 202 | } 203 | 204 | static int step(struct ptrace_info *info) 205 | { 206 | if (ptrace(PTRACE_SINGLESTEP, info->pid, NULL, NULL) == -1) 207 | return -1; 208 | return waitpid(info->pid, NULL, 0); 209 | } 210 | 211 | int ptrace_step_n(struct ptrace_info *info, int n) 212 | { 213 | int i; 214 | 215 | for (i = 0; i < n; i++) 216 | if (step(info) == -1) 217 | return -1; 218 | return 0; 219 | } 220 | 221 | int ptrace_step(struct ptrace_info *info) 222 | { 223 | return ptrace_step_n(info, 1); 224 | } 225 | 226 | int mommy_am_i_inside_a_SO(struct ptrace_info *info) 227 | { 228 | int well_am_i = 0; 229 | struct mem_map_entry *mem_map, *ptr; 230 | struct user_regs_struct regs; 231 | 232 | mem_map = get_process_memory(info); 233 | ptrace_getregs(info, ®s); 234 | 235 | mem_map_foreach(mem_map, ptr) { 236 | uint64_t page_addr = (uint64_t)ptr->addr; 237 | if (regs.rip < page_addr || regs.rip >= page_addr + ptr->size) 238 | continue; 239 | 240 | if (ptr->pathname != NULL) { 241 | well_am_i = !strncmp(ptr->pathname, "/lib", 4) 242 | || !strncmp(ptr->pathname, "/usr/lib", 8); 243 | } 244 | break; 245 | } 246 | 247 | mem_maps_free(mem_map); 248 | return well_am_i; 249 | } 250 | -------------------------------------------------------------------------------- /src/inject.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "ptrace.h" 12 | #include "syscall.h" 13 | 14 | /* 15 | * on how to handle clone https://github.com/lattera/glibc/blob/master/sysdeps/unix/sysv/linux/x86_64/clone.S 16 | */ 17 | 18 | #ifdef DEBUG 19 | #define debug(fmt, ...) do { \ 20 | fprintf(stderr, "[%s:%d:%s()] " fmt, __FILE__,__LINE__, __func__, ##__VA_ARGS__); \ 21 | } while (0) 22 | #else 23 | #define debug(...) 24 | #endif 25 | 26 | #define info(fmt, ...) do { \ 27 | fprintf(stderr, "[+ %s] " fmt, __func__, ##__VA_ARGS__); \ 28 | } while (0) 29 | 30 | struct { 31 | int pid; 32 | char *payload; 33 | } settings = { .pid = -1, .payload = NULL }; 34 | 35 | void parse_args(int argc, char **argv) 36 | { 37 | int opt; 38 | 39 | while ((opt = getopt(argc, argv, "p:f:")) != -1) { 40 | switch (opt) { 41 | case 'p': 42 | settings.pid = atoi(optarg); 43 | break; 44 | case 'f': 45 | settings.payload = strdup(optarg); 46 | break; 47 | default: 48 | fprintf(stderr, "Usage: %s [-p pid]\n", argv[0]); 49 | exit(EXIT_FAILURE); 50 | } 51 | } 52 | 53 | if (settings.pid < 0) { 54 | fprintf(stderr, "Cant attach to pid %d\n", settings.pid); 55 | exit(EXIT_FAILURE); 56 | } 57 | if (!settings.payload) { 58 | fprintf(stderr, "Missing path to payload file\n"); 59 | exit(EXIT_FAILURE); 60 | } 61 | } 62 | 63 | void print_memory_map(struct ptrace_info *info) 64 | { 65 | struct mem_map_entry *ent, *ptr; 66 | ent = get_process_memory(info); 67 | mem_map_foreach(ent, ptr) { 68 | debug("%-18p %07lx %c%c%c %s\n", 69 | ptr->addr, 70 | ptr->size, 71 | ptr->perms & MEM_PERM_READ ? 'r' : '-', 72 | ptr->perms & MEM_PERM_WRITE ? 'w' : '-', 73 | ptr->perms & MEM_PERM_EXEC ? 'x' : '-', 74 | ptr->pathname); 75 | } 76 | debug("%d entries\n", mem_map_length(ent)); 77 | 78 | mem_maps_free(ent); 79 | } 80 | 81 | uint64_t inject_code(struct ptrace_info *info, uint8_t *code, size_t code_len) 82 | { 83 | struct user_regs_struct regs, tmpregs; 84 | uint8_t orig_code[code_len]; 85 | 86 | ptrace_getregs(info, ®s); 87 | 88 | ptrace_readmem(info, (void *)regs.rip, orig_code, sizeof(orig_code)); 89 | ptrace_writemem(info, (void *)regs.rip, code, code_len); 90 | 91 | ptrace_continue(info); 92 | 93 | ptrace_writemem(info, (void *)regs.rip, orig_code, sizeof(orig_code)); 94 | ptrace_getregs(info, &tmpregs); 95 | ptrace_setregs(info, ®s); 96 | 97 | return tmpregs.rax; 98 | } 99 | 100 | uint64_t inject_mmap(struct ptrace_info *info, int size, int prot) 101 | { 102 | uint8_t code[1024]; 103 | int code_len; 104 | 105 | code_len = asm_mmap(code, 0, size, prot, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 106 | code_len += int3(code + code_len); 107 | 108 | return inject_code(info, code, code_len); 109 | } 110 | 111 | uint64_t inject_munmap(struct ptrace_info *info, uint64_t addr, uint64_t len) 112 | { 113 | uint8_t code[1024]; 114 | int code_len; 115 | 116 | code_len = asm_munmap(code, addr, len); 117 | code_len += int3(code + code_len); 118 | 119 | return inject_code(info, code, code_len); 120 | } 121 | 122 | void disas_code_at_addr(struct ptrace_info *info, uint64_t addr, int len, int num_instructions) 123 | { 124 | uint8_t buf[len]; 125 | 126 | ptrace_readmem(info, (void *)addr, buf, len); 127 | disasm(buf, len, addr, num_instructions); 128 | } 129 | 130 | void step_until_no_longer_inside_SO(struct ptrace_info *info, int max_steps) 131 | { 132 | int i; 133 | struct user_regs_struct regs; 134 | 135 | for (i = 0; i < max_steps; ++i) { 136 | if (ptrace_step(info) == -1) 137 | perror("ptrace_step"); 138 | 139 | ptrace_getregs(info, ®s); 140 | if (!mommy_am_i_inside_a_SO(info)) 141 | break; 142 | } 143 | 144 | debug("stepped %d instructions to address %llx\n", i, regs.rip); 145 | } 146 | 147 | void inject_clone(struct ptrace_info *info) 148 | { 149 | FILE *fp; 150 | uint8_t code_to_exec[1024], clone_code[1024]; 151 | int stack_size = 2 * PAGE_SIZE, clone_code_size, codelen; 152 | uint64_t stack_addr, text_addr, bootstrap; 153 | struct user_regs_struct orig_regs, regs; 154 | 155 | if (!(fp = fopen(settings.payload, "r"))) { 156 | perror("fopen"); 157 | return; 158 | } 159 | codelen = fread(code_to_exec, 1, sizeof(code_to_exec), fp); 160 | fclose(fp); 161 | 162 | step_until_no_longer_inside_SO(info, 1024); 163 | 164 | // save registers in their original state 165 | ptrace_getregs(info, &orig_regs); 166 | 167 | text_addr = inject_mmap(info, PAGE_SIZE, PROT_READ|PROT_EXEC); 168 | stack_addr = inject_mmap(info, stack_size, PROT_READ|PROT_WRITE); 169 | bootstrap = inject_mmap(info, PAGE_SIZE, PROT_READ|PROT_EXEC); 170 | debug("allocated memory for text (%lx), stack (%lx), and bootstrap code: (%lx)\n", 171 | text_addr, stack_addr, bootstrap); 172 | 173 | debug("writing code (%d bytes) to execute to target process at address %lx\n", codelen, text_addr); 174 | ptrace_writemem(info, (void *)text_addr, code_to_exec, codelen); 175 | 176 | clone_code_size = asm_clone(clone_code, stack_addr + stack_size, text_addr); 177 | debug("writing clone (%d bytes) syscall to target proces\n", clone_code_size); 178 | ptrace_writemem(info, (void *)bootstrap, clone_code, clone_code_size); 179 | 180 | debug("execute stub code %lx\n", bootstrap); 181 | disas_code_at_addr(info, bootstrap, clone_code_size, 99999); 182 | disas_code_at_addr(info, text_addr, codelen, 99999); 183 | 184 | regs = orig_regs; 185 | regs.rip = bootstrap; 186 | ptrace_setregs(info, ®s); 187 | ptrace_continue(info); 188 | 189 | //debug("munmapping temporary sub code\n"); 190 | //inject_munmap(info, bootstrap, PAGE_SIZE); 191 | 192 | debug("restoring context in main thread\n"); 193 | ptrace_setregs(info, &orig_regs); 194 | } 195 | 196 | void do_the_inject_thing(struct ptrace_info *info) 197 | { 198 | debug("memory map before injection\n"); 199 | print_memory_map(info); 200 | 201 | inject_clone(info); 202 | 203 | debug("memory map after injection\n"); 204 | print_memory_map(info); 205 | } 206 | 207 | int main(int argc, char *argv[]) 208 | { 209 | struct ptrace_info *info; 210 | 211 | parse_args(argc, argv); 212 | 213 | info = ptrace_attach(settings.pid); 214 | if (!info) { 215 | perror("Failed to attach"); 216 | return 1; 217 | } 218 | debug("attached to %d\n", settings.pid); 219 | 220 | do_the_inject_thing(info); 221 | 222 | ptrace_detach(info); 223 | ptrace_free(info); 224 | debug("detached from %d\n", settings.pid); 225 | 226 | free(settings.payload); 227 | 228 | return 0; 229 | } 230 | --------------------------------------------------------------------------------