From f98c503c89fd6fe2fba37992d9ad68f4d504f536 Mon Sep 17 00:00:00 2001 From: micronn Date: Mon, 16 Nov 2020 17:41:52 +0100 Subject: [PATCH 1/3] Add support for execve. --- libptrace_do.c | 188 +++++++++++++++++++++++++++++++++---------------- libptrace_do.h | 4 ++ 2 files changed, 132 insertions(+), 60 deletions(-) diff --git a/libptrace_do.c b/libptrace_do.c index 1e301b2..0af17ab 100644 --- a/libptrace_do.c +++ b/libptrace_do.c @@ -27,6 +27,90 @@ #include "libptrace_do.h" +/********************************************************************** + * + * static int ptrace_do_setup_session(struct ptrace_do *target) + * + * Input: + * The ptrace_do object. + * + * Output: + * 0 on error; non-zero when setup done + * + * Purpose: + * (Re)Initialize the session. Must be called after + * attach, and after execve. + * + **********************************************************************/ +static int ptrace_do_setup_session(struct ptrace_do *target) +{ + int retval; + unsigned long i; + unsigned long peekdata; + struct parse_maps *map_current; + + if((retval = ptrace(PTRACE_GETREGS, target->pid, NULL, &(target->saved_regs))) == -1){ + fprintf(stderr, "%s: ptrace(%d, %d, %lx, %lx): %s\n", program_invocation_short_name, \ + (int) PTRACE_GETREGS, (int) target->pid, (long unsigned int) NULL, \ + (long unsigned int) &(target->saved_regs), strerror(errno)); + return 0; + } + + // The tactic for performing syscall injection is to fill the registers to the appropriate values for your syscall, + // then point $rip at a piece of executable memory that contains the SYSCALL instruction. + + // If we came in from a PTRACE_ATTACH call, then it's likely we are on a syscall edge, and can save time by just + // using the one SIZEOF_SYSCALL addresses behind where we are right now. + errno = 0; + peekdata = ptrace(PTRACE_PEEKTEXT, target->pid, (target->saved_regs).rip - SIZEOF_SYSCALL, NULL); + + if(!errno && ((SYSCALL_MASK & peekdata) == SYSCALL)){ + target->syscall_address = (target->saved_regs).rip - SIZEOF_SYSCALL; + + // Otherwise, we will need to start stepping through the various regions of executable memory looking for + // a SYSCALL instruction. + }else{ + if((target->map_head = get_proc_pid_maps(target->pid)) == NULL){ + fprintf(stderr, "%s: get_proc_pid_maps(%d): %s\n", program_invocation_short_name, \ + (int) target->pid, strerror(errno)); + return 0; + } + + map_current = target->map_head; + while(map_current){ + + if(target->syscall_address){ + break; + } + + if((map_current->perms & MAPS_EXECUTE)){ + + for(i = map_current->start_address; i < (map_current->end_address - sizeof(i)); i++){ + errno = 0; + peekdata = ptrace(PTRACE_PEEKTEXT, target->pid, i, NULL); + if(errno){ + fprintf(stderr, "%s: ptrace(%d, %d, %lx, %lx): %s\n", program_invocation_short_name, \ + (int) PTRACE_PEEKTEXT, (int) target->pid, i, \ + (long unsigned int) NULL, strerror(errno)); + free_parse_maps_list(target->map_head); + return 0; + } + + if((SYSCALL_MASK & peekdata) == SYSCALL){ + target->syscall_address = i; + break; + } + } + } + + map_current = map_current->next; + } + + free_parse_maps_list(target->map_head); + } + + return 1; +} /********************************************************************** * @@ -45,13 +129,9 @@ **********************************************************************/ struct ptrace_do *ptrace_do_init(int pid){ int retval, status; - unsigned long peekdata; - unsigned long i; struct ptrace_do *target; siginfo_t siginfo; - struct parse_maps *map_current; - if((target = (struct ptrace_do *) malloc(sizeof(struct ptrace_do))) == NULL){ fprintf(stderr, "%s: malloc(%d): %s\n", program_invocation_short_name, \ @@ -100,66 +180,11 @@ struct ptrace_do *ptrace_do_init(int pid){ } } - if((retval = ptrace(PTRACE_GETREGS, target->pid, NULL, &(target->saved_regs))) == -1){ - fprintf(stderr, "%s: ptrace(%d, %d, %lx, %lx): %s\n", program_invocation_short_name, \ - (int) PTRACE_GETREGS, (int) target->pid, (long unsigned int) NULL, \ - (long unsigned int) &(target->saved_regs), strerror(errno)); + if (!ptrace_do_setup_session(target)) { free(target); return(NULL); } - // The tactic for performing syscall injection is to fill the registers to the appropriate values for your syscall, - // then point $rip at a piece of executable memory that contains the SYSCALL instruction. - - // If we came in from a PTRACE_ATTACH call, then it's likely we are on a syscall edge, and can save time by just - // using the one SIZEOF_SYSCALL addresses behind where we are right now. - errno = 0; - peekdata = ptrace(PTRACE_PEEKTEXT, target->pid, (target->saved_regs).rip - SIZEOF_SYSCALL, NULL); - - if(!errno && ((SYSCALL_MASK & peekdata) == SYSCALL)){ - target->syscall_address = (target->saved_regs).rip - SIZEOF_SYSCALL; - - // Otherwise, we will need to start stepping through the various regions of executable memory looking for - // a SYSCALL instruction. - }else{ - if((target->map_head = get_proc_pid_maps(target->pid)) == NULL){ - fprintf(stderr, "%s: get_proc_pid_maps(%d): %s\n", program_invocation_short_name, \ - (int) target->pid, strerror(errno)); - free(target); - return(NULL); - } - - map_current = target->map_head; - while(map_current){ - - if(target->syscall_address){ - break; - } - - if((map_current->perms & MAPS_EXECUTE)){ - - for(i = map_current->start_address; i < (map_current->end_address - sizeof(i)); i++){ - errno = 0; - peekdata = ptrace(PTRACE_PEEKTEXT, target->pid, i, NULL); - if(errno){ - fprintf(stderr, "%s: ptrace(%d, %d, %lx, %lx): %s\n", program_invocation_short_name, \ - (int) PTRACE_PEEKTEXT, (int) target->pid, i, \ - (long unsigned int) NULL, strerror(errno)); - free(target); - free_parse_maps_list(target->map_head); - return(NULL); - } - - if((SYSCALL_MASK & peekdata) == SYSCALL){ - target->syscall_address = i; - break; - } - } - } - - map_current = map_current->next; - } - } return(target); } @@ -524,6 +549,18 @@ unsigned long ptrace_do_syscall(struct ptrace_do *target, unsigned long rax, \ kill(target->pid, sig_remember); } + // Invalidate local memory and reinitialize session after execve + if ((rax == __NR_execve || rax == __NR_execveat) && (int64_t)attack_regs.rax >= 0) { + ptrace_do_clear_local(target); + if (!ptrace_do_setup_session(target)) { + fprintf(stderr, "%s: ptrace_do_setup_session(%lx)\n", program_invocation_short_name, \ + (long unsigned int) target); + return(-1); + } + errno = 0; + return attack_regs.rax; + } + // Let's reset this to what it was upon entry. if((retval = ptrace(PTRACE_SETREGS, target->pid, NULL, &(target->saved_regs))) == -1){ fprintf(stderr, "%s: ptrace(%d, %d, %lx, %lx): %s\n", program_invocation_short_name, \ @@ -598,6 +635,37 @@ void ptrace_do_cleanup(struct ptrace_do *target){ free(target); } +/********************************************************************** + * + * void ptrace_do_clear_local(struct ptrace_do *target) + * + * Input: + * This sessions ptrace_do_object. + * + * Output: + * None. + * + * Purpose: + * To dispose all local objects. This is necessary when + * the objects become invalid, for example, after a call + * to execve. + * + **********************************************************************/ +void ptrace_do_clear_local(struct ptrace_do *target) +{ + struct mem_node *this_node, *previous_node; + + this_node = target->mem_head; + while (this_node) { + free(this_node->local_address); + previous_node = this_node; + this_node = this_node->next; + free(previous_node); + } + + target->mem_head = NULL; +} + /********************************************************************** * diff --git a/libptrace_do.h b/libptrace_do.h index cd5c7f1..881e71b 100644 --- a/libptrace_do.h +++ b/libptrace_do.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -80,6 +81,9 @@ void *ptrace_do_get_remote_addr(struct ptrace_do *target, void *local_addr); unsigned long ptrace_do_syscall(struct ptrace_do *target, unsigned long rax, \ unsigned long rdi, unsigned long rsi, unsigned long rdx, unsigned long r10, unsigned long r8, unsigned long r9); +/* ptrace_do_clear_local() will clear all local allocated memory */ +void ptrace_do_clear_local(struct ptrace_do *target); + /* ptrace_do_cleanup() will detatch and do it's best to clean up the data structures. */ void ptrace_do_cleanup(struct ptrace_do *target); From 4b333135e3b8bed193e27f7e51360b1cebef0c35 Mon Sep 17 00:00:00 2001 From: micronn Date: Mon, 16 Nov 2020 18:32:37 +0100 Subject: [PATCH 2/3] Fix address calculation after execve. --- libptrace_do.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libptrace_do.c b/libptrace_do.c index 0af17ab..e95112d 100644 --- a/libptrace_do.c +++ b/libptrace_do.c @@ -49,6 +49,8 @@ static int ptrace_do_setup_session(struct ptrace_do *target) unsigned long peekdata; struct parse_maps *map_current; + target->syscall_address = 0; + if((retval = ptrace(PTRACE_GETREGS, target->pid, NULL, &(target->saved_regs))) == -1){ fprintf(stderr, "%s: ptrace(%d, %d, %lx, %lx): %s\n", program_invocation_short_name, \ (int) PTRACE_GETREGS, (int) target->pid, (long unsigned int) NULL, \ From f9b0e23dd0324ff56fafd088c51182dfe7559292 Mon Sep 17 00:00:00 2001 From: micronn Date: Mon, 16 Nov 2020 18:48:46 +0100 Subject: [PATCH 3/3] Update README.md to explain execve side effects. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5161e68..cca2740 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,9 @@ Here is the brief list of function interfaces. These functions are documented in /* ptrace_do_free() frees a joint memory object. "operation" refers to the FREE_* modes above. */ void ptrace_do_free(struct ptrace_do *target, void *local_address, int operation); + + /* ptrace_do_clear_local() frees allocated memory ignoring remote allocations (used when mmaps change, for example, after execve) */ + void ptrace_do_clear_local(struct ptrace_do *target); /* ptrace_do_push_mem() and ptrace_do_pull_mem() synchronize the memory states between local and remote buffers. */ void *ptrace_do_push_mem(struct ptrace_do *target, void *local_address); @@ -90,6 +93,8 @@ Here is the brief list of function interfaces. These functions are documented in /* Mostly for debugging, but in case it comes in handy, this function prints the parse_maps object members. */ void dump_parse_maps_list(struct parse_maps *head); +Note that if an execve syscall is executed, all memory allocations will be invalidated. + ## Installation ## git clone https://github.com/emptymonkey/ptrace_do.git