├── .gitattributes ├── .gitconfig ├── .gitignore ├── Makefile ├── README.md ├── ftrace.c ├── functools.h ├── logging.h ├── ptrace_helpers.h ├── readelf.h └── test.c /.gitattributes: -------------------------------------------------------------------------------- 1 | *.md -crlf 2 | *.h -crlf 3 | *.c -crlf 4 | *.py -crlf 5 | Makefile -crlf 6 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [core] 2 | autocrlf = false 3 | eol = lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | 35 | sftp-config.json 36 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS= 3 | 4 | DEPS=functools.h readelf.h ptrace_helpers.h logging.h 5 | 6 | ftrace: ftrace.c functools.h readelf.h ptrace_helpers.h logging.h 7 | $(CC) -o ftrace ftrace.c -lcapstone $(CFLAGS) 8 | 9 | test: test.c 10 | $(CC) -o test test.c 11 | 12 | clean: 13 | rm ftrace -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ftrace 2 | > ltrace/strace but for local functions 3 | 4 | ## Contents 5 | - [Introduction](#introduction) 6 | - [Installation](#installation) 7 | - [Usage](#usage) 8 | - [Example](#example) 9 | - [Future work](#future-work) 10 | - [Dependencies](#dependencies) 11 | - [Limitations](#limitations) 12 | 13 | ## Introduction 14 | The basic idea behind the implementation is: 15 | 16 | 1. Read in ELF file and identify symbols 17 | 2. Fork and ptrace target process 18 | 3. Add breakpoints to all symbols 19 | 4. Catch breakpoints and log function call; additionally, add a temp. breakpoint to return pointer (for decreasing depthing and logging return value) 20 | 5. Repeat 21 | 22 | Some other fancy stuff happens in the background. For instance, if no header file is provided simple taint analysis is done on functions to determine the number of function arguments. 23 | 24 | ## Installation 25 | 26 | ## Usage 27 | 28 | > `./ftrace [arg 1] [arg2] ...` 29 | 30 | ### Optional parameters 31 | - `-C` - adds colored output 32 | - `-H ` - header file to use for function logging 33 | - `-R` - display function return values 34 | - `-o ` - specifies output file (replaces stderr) 35 | - `-h` - display this message 36 | 37 | ## Example 38 | 39 | ### test.c 40 | ``` c 41 | #include 42 | #include 43 | 44 | int fib(int n) { 45 | if (n == 0 || n == 1) return 1; 46 | else return fib(n - 1) + fib(n - 2); 47 | } 48 | 49 | int main(int argc, char **argv) { 50 | int n = atoi(argv[1]); 51 | printf("%d\n", fib(n)); 52 | return 0; 53 | } 54 | ``` 55 | 56 | 57 | ``` 58 | $ gcc -o test test.c 59 | $ ./ftrace ./test 3 60 | _start() 61 | __libc_csu_init(2, *0x7ffe0cb39158, *0x7ffe0cb39170) 62 | main(2, *0x7ffe0cb39158) 63 | fib(3) 64 | fib(2) 65 | fib(1) 66 | fib(0) 67 | fib(1) 68 | 3 69 | ``` 70 | 71 | 72 | ## Future work 73 | 74 | ## Dependencies 75 | 76 | * [Capstone Version 4.0-alpha3](https://github.com/aquynh/capstone/releases/tag/4.0-alpha3) 77 | 78 | ## Limitations 79 | - Currently only compatible with 64 bit ELF files. 80 | -------------------------------------------------------------------------------- /ftrace.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "readelf.h" 11 | #include "functools.h" 12 | #include "ptrace_helpers.h" 13 | #include "logging.h" 14 | 15 | #define SIG_LEN 100 16 | 17 | typedef struct callstack { 18 | char sig[SIG_LEN]; 19 | struct callstack *prev; 20 | } callstack_t; 21 | 22 | const uint8_t trap_inst = 0xCC; 23 | 24 | const char *blacklist[] = {"", "frame_dummy", "register_tm_clones", 25 | "deregister_tm_clones", "__do_global_dtors_aux"}; 26 | 27 | void usage(char *prog); 28 | 29 | void add_call(char sig[SIG_LEN]); 30 | char *get_call(); 31 | void delete_call(); 32 | 33 | int add_breakpoint(void *addr); 34 | int restore_code(void *addr, int len, struct elf *e); 35 | 36 | bool in_blacklist(char *name); 37 | int register_functions(struct elf *e); 38 | 39 | void print_depth(int d); 40 | void trace(pid_t pid); 41 | void traced(); 42 | 43 | format_t *func_fmts = NULL; 44 | callstack_t *call_stack = NULL; 45 | 46 | // globals from command line arguments 47 | bool show_ret = false; 48 | char **traced_argv; 49 | char *call_fmt = "%s\n"; 50 | char *ret_fmt = NULL; 51 | 52 | pid_t child; 53 | 54 | void usage(char *prog) { 55 | printf("Usage: %s [arg 1] [arg2] ...\n\n" 56 | 57 | "Optional parameters:\n" 58 | " -C - adds colored output\n" 59 | " -H - header file to use for function logging\n" 60 | " -R - display function return values\n" 61 | " -o - specifies output file (replaces stderr)\n" 62 | " -h - display this message\n\n", 63 | 64 | prog); 65 | exit(1); 66 | } 67 | 68 | void add_call(char sig[SIG_LEN]) { 69 | callstack_t *new_call; 70 | 71 | new_call = malloc(sizeof(*new_call)); 72 | memcpy(new_call->sig, sig, SIG_LEN); 73 | new_call->prev = call_stack; 74 | call_stack = new_call; 75 | } 76 | 77 | char *get_call() { 78 | if (call_stack == NULL) return NULL; 79 | else return call_stack->sig; 80 | } 81 | 82 | void delete_call() { 83 | callstack_t *tmp; 84 | 85 | if (call_stack == NULL) return; 86 | 87 | tmp = call_stack; 88 | call_stack = call_stack->prev; 89 | free(tmp); 90 | } 91 | 92 | int add_breakpoint(void *addr) { 93 | uint8_t bp[1] = {trap_inst}; 94 | return write_data(child, addr, bp, sizeof(bp)); 95 | } 96 | 97 | int restore_code(void *addr, int len, struct elf *e) { 98 | return write_data(child, addr, bytes_from_addr_in_section(e, addr, ".text"), len); 99 | } 100 | 101 | bool in_blacklist(char *name) { 102 | int i; 103 | 104 | for (i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); i++) { 105 | if (!strcmp(name, blacklist[i])) return true; 106 | } 107 | 108 | return false; 109 | } 110 | 111 | int register_functions(struct elf *e) { 112 | int i; 113 | 114 | for (i = 0; i < e->n_syms; i++) { 115 | if (sym_in_section(e, i, ".text") && !in_blacklist(get_sym_name(e, i))) { 116 | if (add_breakpoint(get_sym_addr(e, i)) == -1) return -1; 117 | func_fmts = add_format(func_fmts, get_sym_addr(e, i), i, NULL); 118 | } 119 | } 120 | 121 | return 0; 122 | } 123 | 124 | void print_depth(int d) { 125 | while (d--) trace_print(RESET, " "); 126 | } 127 | 128 | void trace(pid_t pid) { 129 | int status, fd; 130 | struct user_regs_struct regs; 131 | int sym_i, depth; 132 | void *bp_addr, *ret_addr; 133 | format_t *fmt; 134 | char fmt_buf[FMT_LEN], sig[SIG_LEN]; 135 | struct elf *e; 136 | 137 | child = pid; 138 | 139 | fd = open(traced_argv[0], O_RDONLY); 140 | if (fd == -1) error("failed to open file"); 141 | 142 | e = readelf(fd); 143 | if (e == NULL) error("failed to read elf file for symbols"); 144 | 145 | wait(&status); 146 | 147 | register_functions(e); 148 | ptrace(PTRACE_CONT, child, NULL, NULL); 149 | 150 | depth = 0; 151 | while (1) { 152 | wait(&status); 153 | 154 | if (WIFEXITED(status) || WIFSIGNALED(status)) { 155 | // printf("child exited of signal %d\n", WSTOPSIG(status)); 156 | break; 157 | } 158 | 159 | if (WSTOPSIG(status) == SIGTRAP) { 160 | if (ptrace(PTRACE_GETREGS, child, NULL, ®s) == -1) error("error getting regs"); 161 | 162 | bp_addr = bp_addr = (void *) (regs.rip - 1); 163 | sym_i = at_symbol(e, bp_addr); // minus 1 because int 3 already executed 164 | 165 | if (sym_i == -1) { 166 | // we are returning from a local function call 167 | depth -= 1; 168 | 169 | if (ret_fmt != NULL) { 170 | print_depth(depth * 2); 171 | trace_print(RED, ret_fmt, get_call(), regs.rax); 172 | } 173 | 174 | delete_call(); 175 | } else { 176 | // sym_i != -1 therefore we are at a function breakpoint 177 | fmt = get_format(func_fmts, bp_addr); 178 | 179 | if (!fmt->fancy) { 180 | memset(fmt_buf, 0, sizeof(fmt_buf)); 181 | fancy_func_fmt(e, fmt->sym_i, fmt_buf, sizeof(fmt_buf), child); 182 | update_format(func_fmts, fmt->addr, fmt_buf); 183 | fmt->fancy = true; 184 | } 185 | 186 | snprintf(sig, SIG_LEN - 1, fmt->str, regs.rdi, regs.rsi, regs.rdx); 187 | add_call(sig); 188 | 189 | print_depth(depth * 2); 190 | trace_print(GREEN, call_fmt, sig); 191 | 192 | ret_addr = (void *) ptrace(PTRACE_PEEKTEXT, child, regs.rsp, NULL); 193 | if (addr_in_section(e, ret_addr, ".text")) { 194 | add_breakpoint(ret_addr); 195 | depth += 1; 196 | } 197 | } 198 | 199 | regs.rip -= 1; 200 | ptrace(PTRACE_SETREGS, child, NULL, ®s); 201 | restore_code(bp_addr, 1, e); 202 | 203 | ptrace(PTRACE_SINGLESTEP, child, NULL, NULL); 204 | wait(&status); 205 | 206 | add_breakpoint(bp_addr); 207 | 208 | ptrace(PTRACE_CONT, child, NULL, NULL); 209 | } else { 210 | // wasn't our breakpoint -- deliver signal to child 211 | ptrace(PTRACE_CONT, child, NULL, WSTOPSIG(status)); 212 | } 213 | } 214 | } 215 | 216 | void traced() { 217 | if (ptrace(PTRACE_TRACEME, NULL, NULL, NULL) == -1) error("traceme failed"); 218 | if (execvp(traced_argv[0], traced_argv) == -1) error("could not execute file"); 219 | } 220 | 221 | int main(int argc, char **argv) { 222 | pid_t pid; 223 | char opt; 224 | 225 | if (argc == 1) usage(argv[0]); 226 | 227 | while ((opt = getopt(argc, argv, "+CH:Ro:h")) != -1) { 228 | switch (opt) { 229 | case 'C': 230 | colored = true; 231 | break; 232 | case 'H': 233 | // read in header file and set formats accordingly 234 | error("Option 'H' not yet supported sorry!"); 235 | break; 236 | case 'R': 237 | call_fmt = ">> %s\n"; 238 | ret_fmt = "<< %s = %d\n"; 239 | break; 240 | case 'o': 241 | trace_fd = open(optarg, O_WRONLY | O_CREAT | O_TRUNC); 242 | if (trace_fd == -1) error("Couldn't open output file %s", optarg); 243 | break; 244 | case 'h': 245 | default: 246 | usage(argv[0]); 247 | break; 248 | } 249 | } 250 | 251 | traced_argv = argv + optind; 252 | 253 | if (access(traced_argv[0], R_OK | X_OK) == -1) error("Unable to open %s", traced_argv[0]); 254 | 255 | if ((pid = fork()) == -1) error("failed to fork"); 256 | 257 | if (pid == 0) traced(); 258 | else trace(pid); 259 | 260 | return 0; 261 | } -------------------------------------------------------------------------------- /functools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "readelf.h" 8 | #include "ptrace_helpers.h" 9 | #include "logging.h" 10 | 11 | #define FMT_LEN 100 12 | 13 | typedef enum reg_state { 14 | REG_UNDEF, 15 | REG_WRITTEN, 16 | REG_READ 17 | } reg_state; 18 | 19 | typedef struct format { 20 | void *addr; 21 | int sym_i; 22 | char str[FMT_LEN]; 23 | bool fancy; 24 | struct format *next; 25 | } format_t; 26 | 27 | format_t *add_format(format_t *fmt, void *addr, int sym_i, char *str) { 28 | format_t *new_fmt; 29 | 30 | new_fmt = malloc(sizeof(*new_fmt)); 31 | if (new_fmt == NULL) return NULL; 32 | 33 | new_fmt->addr = addr; 34 | new_fmt->sym_i = sym_i; 35 | if (str == NULL) new_fmt->str[0] = 0; 36 | else strncpy(new_fmt->str, str, sizeof(new_fmt->str) - 1); 37 | new_fmt->fancy = false; 38 | new_fmt->next = fmt; 39 | 40 | return new_fmt; 41 | } 42 | 43 | format_t *get_format(format_t *fmt, void *addr) { 44 | while (fmt != NULL) { 45 | if (fmt->addr == addr) return fmt; 46 | fmt = fmt->next; 47 | } 48 | 49 | return NULL; 50 | } 51 | 52 | bool update_format(format_t *fmt, void *addr, char *str) { 53 | while (fmt != NULL) { 54 | if (fmt->addr == addr) { 55 | strncpy(fmt->str, str, sizeof(fmt->str) - 1); 56 | return true; 57 | } 58 | fmt = fmt->next; 59 | } 60 | 61 | return false; 62 | } 63 | 64 | void print_formats(format_t *fmt) { 65 | if (fmt == NULL) return; 66 | printf("[%p] ", fmt->addr); 67 | puts(fmt->str); 68 | print_formats(fmt->next); 69 | } 70 | 71 | int get_reg_arg_index(x86_reg reg) { 72 | switch (reg) { 73 | case X86_REG_RDI: 74 | case X86_REG_EDI: 75 | return 0; 76 | case X86_REG_RSI: 77 | case X86_REG_ESI: 78 | return 1; 79 | case X86_REG_RDX: 80 | case X86_REG_EDX: 81 | return 2; 82 | case X86_REG_RCX: 83 | case X86_REG_ECX: 84 | return 3; 85 | default: 86 | return -1; 87 | } 88 | } 89 | 90 | int n_args_from_regs(reg_state *arg_regs, int n) { 91 | int i; 92 | 93 | for (i = 0; i < n; i++) { 94 | if (arg_regs[i] != REG_READ) return i; 95 | } 96 | 97 | return i; 98 | } 99 | 100 | // gets number of arguments for x86_64 system v abi assuming no passing structs 101 | // by value 102 | int n_func_args(struct elf *e, int sym_i) { 103 | void *func; 104 | uint8_t *code; 105 | size_t size, count, i, n; 106 | csh handle; 107 | cs_insn *all_insn, *insn; 108 | cs_detail *detail; 109 | cs_regs regs_read, regs_written; 110 | uint8_t read_count, write_count; 111 | int reg_i; 112 | reg_state arg_regs[] = {REG_UNDEF, REG_UNDEF, REG_UNDEF, REG_UNDEF}; 113 | 114 | func = get_sym_addr(e, sym_i); 115 | code = bytes_from_addr_in_section(e, get_sym_addr(e, sym_i), ".text"); 116 | size = get_sym_size(e, sym_i); 117 | 118 | if (size == 0) { 119 | printf("no size information for %s\n", get_sym_name(e, sym_i)); 120 | return -1; 121 | } 122 | 123 | if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) { 124 | printf("unable to initilize handle\n"); 125 | return -1; 126 | } 127 | 128 | cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); 129 | 130 | count = cs_disasm(handle, code, size, (uint64_t) func, 0, &all_insn); 131 | 132 | // printf("%s [%p] size: %lx, count: %lx\n", 133 | // get_sym_name(e, sym_i), func, size, count); 134 | 135 | for (i = 0; i < count; i++) { 136 | insn = &all_insn[i]; 137 | if (cs_regs_access(handle, insn, 138 | regs_read, &read_count, 139 | regs_written, &write_count)) return -1; 140 | // printf("\t%s %s [r: %u, w: %u]\n", insn->mnemonic, insn->op_str, read_count, write_count); 141 | // if (read_count > 0) printf("\t\tread:\n"); 142 | // for (n = 0; n < read_count; n++) { 143 | // printf("\t\t\t%d\n", get_reg_arg_index(regs_read[n])); 144 | // } 145 | for (n = 0; n < read_count; n++) { 146 | reg_i = get_reg_arg_index(regs_read[n]); 147 | if (reg_i == -1) continue; 148 | if (arg_regs[reg_i] != REG_WRITTEN) arg_regs[reg_i] = REG_READ; 149 | } 150 | for (n = 0; n < write_count; n++) { 151 | reg_i = get_reg_arg_index(regs_written[n]); 152 | if (reg_i == -1) continue; 153 | if (arg_regs[reg_i] != REG_READ) arg_regs[reg_i] = REG_WRITTEN; 154 | } 155 | } 156 | 157 | return n_args_from_regs(arg_regs, 4); 158 | } 159 | 160 | int basic_func_fmt(struct elf *e, int sym_i, char *buf, int n) { 161 | int n_args, i; 162 | 163 | strncat(buf, get_sym_name(e, sym_i), n); 164 | strncat(buf, "(", n); 165 | 166 | n_args = n_func_args(e, sym_i); 167 | 168 | for (i = 0; i < n_args - 1; i++) { 169 | strncat(buf, "0x%lx, ", n); 170 | } 171 | if (n_args > 0) strncat(buf, "0x%lx", n); 172 | strncat(buf, ")", n); 173 | 174 | return 0; 175 | } 176 | 177 | uint64_t get_n_arg(struct user_regs_struct *regs, int n) { 178 | switch (n) { 179 | case 0: 180 | return regs->rdi; 181 | case 1: 182 | return regs->rsi; 183 | case 2: 184 | return regs->rdx; 185 | case 3: 186 | return regs->rcx; 187 | default: 188 | return (uint64_t) -1; 189 | } 190 | } 191 | 192 | #define MAX_READ 20 193 | 194 | char *get_arg_fmt(uint64_t arg, pid_t pid) { 195 | uint64_t val; 196 | uint8_t buf[20]; 197 | 198 | errno = 0; 199 | val = ptrace(PTRACE_PEEKDATA, pid, arg, NULL); 200 | if (errno != 0) return "%lu"; 201 | 202 | // read_data(pid, (void *) arg, buf, sizeof(buf)); 203 | 204 | // if (buf[0] == '4') { 205 | // puts((char *) buf); 206 | // return "\"%s\""; 207 | // } 208 | 209 | return "*%p"; 210 | } 211 | 212 | int fancy_func_fmt(struct elf *e, int sym_i, char *buf, int n, pid_t pid) { 213 | int n_args, i; 214 | struct user_regs_struct regs; 215 | 216 | strncat(buf, get_sym_name(e, sym_i), n); 217 | strncat(buf, "(", n); 218 | 219 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 220 | 221 | n_args = n_func_args(e, sym_i); 222 | 223 | for (i = 0; i < n_args - 1; i++) { 224 | strncat(buf, get_arg_fmt(get_n_arg(®s, i), pid), n); 225 | strncat(buf, ", ", n); 226 | } 227 | if (n_args > 0) strncat(buf, get_arg_fmt(get_n_arg(®s, i), pid), n); 228 | strncat(buf, ")", n); 229 | 230 | return 0; 231 | } -------------------------------------------------------------------------------- /logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define RED "\x1b[31m" 9 | #define GREEN "\x1b[32m" 10 | #define YELLOW "\x1b[33m" 11 | #define RESET "\x1b[0m" 12 | 13 | // this flag determines whether these functions actually print anything 14 | bool verbose = true; 15 | int trace_fd = 2; 16 | bool colored = false; 17 | 18 | // pass through to printf that can be disabled by the verbose flag [white] 19 | void trace_print(char *color, char *msg, ...) { 20 | va_list args; 21 | 22 | if (!verbose) return; 23 | 24 | va_start(args, msg); 25 | 26 | if (colored) dprintf(trace_fd, "%s", color); 27 | vdprintf(trace_fd, msg, args); 28 | if (colored) dprintf(trace_fd, "%s", RESET); 29 | } 30 | 31 | // logs an error to the console and quits - only for extreme errors [red] 32 | void error(char *msg, ...) { 33 | va_list args; 34 | 35 | va_start(args, msg); 36 | 37 | printf("%s[!] Error: ", RED); 38 | vprintf(msg, args); 39 | printf("%s\n", RESET); 40 | exit(1); 41 | } 42 | 43 | // logs an info [green] 44 | void info(char *msg, ...) { 45 | va_list args; 46 | 47 | if (!verbose) return; 48 | 49 | va_start(args, msg); 50 | 51 | printf("%s[+] ", GREEN); 52 | vprintf(msg, args); 53 | printf("%s\n", RESET); 54 | } 55 | 56 | // logs a warning [yellow] 57 | void warn(char *msg, ...) { 58 | va_list args; 59 | 60 | if (!verbose) return; 61 | 62 | va_start(args, msg); 63 | 64 | printf("%s[-] ", YELLOW); 65 | vprintf(msg, args); 66 | printf("%s\n", RESET); 67 | } -------------------------------------------------------------------------------- /ptrace_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | int read_data(pid_t pid, void *addr, uint8_t *buf, int len) { 6 | int remaining; 7 | union { 8 | uint64_t val; 9 | char bytes[sizeof(uint64_t)]; 10 | } u; // union idea taken from http://www.linuxjournal.com/article/6210 11 | 12 | while (len) { 13 | if (len < sizeof(uint64_t)) break; 14 | 15 | errno = 0; 16 | u.val = ptrace(PTRACE_PEEKDATA, pid, addr, NULL); 17 | if (u.val == -1 && errno != 0) return -1; 18 | memcpy(buf, u.bytes, sizeof(uint64_t)); 19 | 20 | len -= sizeof(uint64_t); 21 | buf += sizeof(uint64_t); 22 | addr += sizeof(uint64_t); 23 | } 24 | 25 | errno = 0; 26 | u.val = ptrace(PTRACE_PEEKTEXT, pid, addr, NULL); 27 | if (u.val == -1 && errno != 0) return -1; 28 | memcpy(buf, u.bytes, len % sizeof(uint64_t)); 29 | 30 | return 0; 31 | } 32 | 33 | int write_data(pid_t child, void *addr, uint8_t *data, int len) { 34 | int remaining; 35 | union { 36 | uint64_t val; 37 | char bytes[sizeof(uint64_t)]; 38 | } u; // union idea taken from http://www.linuxjournal.com/article/6210 39 | 40 | while (len) { 41 | if (len < sizeof(uint64_t)) break; 42 | 43 | if (ptrace(PTRACE_POKETEXT, child, addr, data) == -1) return -1; 44 | 45 | len -= sizeof(uint64_t); 46 | data += sizeof(uint64_t); 47 | addr += sizeof(uint64_t); 48 | } 49 | 50 | errno = 0; 51 | u.val = ptrace(PTRACE_PEEKTEXT, child, addr, NULL); 52 | if (u.val == -1 && errno != 0) return -1; 53 | // printf("before: %lx\n", u.val); 54 | memcpy(u.bytes, data, len % sizeof(uint64_t)); 55 | if (ptrace(PTRACE_POKETEXT, child, addr, u.val) == -1) return -1; 56 | // u.val = ptrace(PTRACE_PEEKTEXT, child, addr, NULL); 57 | // printf("after: %lx\n", u.val); 58 | 59 | return 0; 60 | } -------------------------------------------------------------------------------- /readelf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct elf { 11 | uint8_t *file; 12 | Elf64_Ehdr ehdr; 13 | Elf64_Phdr *phdrs; 14 | int n_phdrs; 15 | Elf64_Shdr *shdrs; 16 | int n_shdrs; 17 | char *shdr_names; 18 | Elf64_Sym *syms; 19 | int n_syms; 20 | char *sym_names; 21 | }; 22 | 23 | char *get_shdr_name(struct elf *e, int i) { 24 | return &e->shdr_names[e->shdrs[i].sh_name]; 25 | } 26 | 27 | void *get_sym_addr(struct elf *e, int i) { 28 | return (void *) e->syms[i].st_value; 29 | } 30 | 31 | char *get_sym_name(struct elf *e, int i) { 32 | return &e->sym_names[e->syms[i].st_name]; 33 | } 34 | 35 | size_t get_sym_size(struct elf *e, int i) { 36 | return e->syms[i].st_size; 37 | } 38 | 39 | int get_sym_index(struct elf *e, char *name) { 40 | int i; 41 | 42 | for (i = 0; i < e->n_syms; i++) { 43 | if (!strcmp(name, get_sym_name(e, i))) return i; 44 | } 45 | 46 | return -1; 47 | } 48 | 49 | int at_symbol(struct elf *e, void *addr) { 50 | int i; 51 | 52 | for (i = 0; i < e->n_syms; i++) { 53 | if (get_sym_addr(e, i) == addr) return i; 54 | } 55 | 56 | return -1; 57 | } 58 | 59 | Elf64_Shdr *get_shdr(struct elf *e, char *name) { 60 | int i; 61 | 62 | for (i = 0; i < e->n_shdrs; i++) { 63 | if (!strcmp(name, get_shdr_name(e, i))) return &e->shdrs[i]; 64 | } 65 | 66 | return NULL; 67 | } 68 | 69 | bool addr_in_section(struct elf *e, void *addr, char *name) { 70 | Elf64_Shdr *shdr; 71 | uint64_t sec_start, sec_end; 72 | 73 | shdr = get_shdr(e, name); 74 | sec_start = shdr->sh_addr; 75 | sec_end = sec_start + shdr->sh_size; 76 | 77 | return (uint64_t) addr >= sec_start && (uint64_t) addr < sec_end; 78 | } 79 | 80 | bool sym_in_section(struct elf *e, int i, char *name) { 81 | return addr_in_section(e, get_sym_addr(e, i), name); 82 | } 83 | 84 | uint8_t *bytes_from_addr_in_section(struct elf *e, void *addr, char *name) { 85 | Elf64_Shdr *shdr; 86 | 87 | shdr = get_shdr(e, name); 88 | 89 | return &e->file[(uint64_t) addr - (uint64_t) shdr->sh_addr + shdr->sh_offset]; 90 | } 91 | 92 | struct elf *readelf(int fd) { 93 | struct stat st; 94 | struct elf *e; 95 | Elf64_Shdr *sym_hdr; 96 | 97 | e = malloc(sizeof(*e)); 98 | 99 | fstat(fd, &st); 100 | e->file = malloc(st.st_size); 101 | read(fd, e->file, st.st_size); 102 | 103 | e->ehdr = *(Elf64_Ehdr *) e->file; 104 | 105 | e->phdrs = (Elf64_Phdr *) &e->file[e->ehdr.e_phoff]; 106 | e->n_phdrs = e->ehdr.e_phnum; 107 | 108 | e->shdrs = (Elf64_Shdr *) &e->file[e->ehdr.e_shoff]; 109 | e->n_shdrs = e->ehdr.e_shnum; 110 | e->shdr_names = &e->file[e->shdrs[e->ehdr.e_shstrndx].sh_offset]; 111 | 112 | sym_hdr = get_shdr(e, ".symtab"); 113 | 114 | e->syms = (Elf64_Sym *) &e->file[sym_hdr->sh_offset]; 115 | e->n_syms = sym_hdr->sh_size / sym_hdr->sh_entsize; 116 | e->sym_names = &e->file[e->shdrs[sym_hdr->sh_link].sh_offset]; 117 | 118 | return e; 119 | } -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int fib(int n) { 5 | if (n == 0) return 1; 6 | if (n == 1) return 1; 7 | else return fib(n - 1) + fib(n - 2); 8 | } 9 | 10 | int get_n(char *str) { 11 | char buf[10]; 12 | 13 | if (str == NULL) { 14 | printf(">> "); 15 | fgets(buf, sizeof(buf), stdin); 16 | return atoi(buf); 17 | } else return atoi(str); 18 | } 19 | 20 | int main(int argc, char **argv) { 21 | int n; 22 | 23 | if (argc == 2) n = get_n(argv[1]); 24 | else n = get_n(NULL); 25 | 26 | printf("%d\n", fib(n)); 27 | 28 | return 0; 29 | } --------------------------------------------------------------------------------