├── .gitignore ├── AUTHORS ├── Makefile ├── README ├── TODO ├── src ├── helpers │ └── helpers.cc ├── includes │ ├── colors.hh │ ├── defines.hh │ ├── helpers.hh │ ├── level1.hh │ ├── level2.hh │ ├── level4.hh │ ├── shared.hh │ └── syscalls.hh ├── level1 │ ├── mem_strace.cc │ ├── strace.cc │ └── syscalls.cc ├── level2 │ ├── breaker.cc │ ├── dig_into_mem.cc │ └── mem_strace_hook.cc ├── level3 │ ├── mem_tracker.cc │ ├── memory_hooks.c │ └── tracker.cc └── level4 │ ├── injector.cc │ ├── mem_checker.cc │ └── sanity_check.cc ├── tests └── debug.cc └── utils └── repo_cleaner.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | *.so 3 | *.o 4 | *.a 5 | *.png 6 | *.bckp 7 | memcheck 8 | my_strace 9 | hardcoded 10 | .#* 11 | .gdb* 12 | *.txt 13 | mem_strace 14 | mem_strace_hook 15 | mem_tracker 16 | mem_checker 17 | *.out 18 | debug 19 | libhooks.so 20 | WHATEVER -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * lejay_s 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # LSE RECRUITEMENT 2016 - my_memcheck 2 | # 3 | # Project was supposed to be done in C++ 4 | # But I found some of it to be easier to do 5 | # in C. Like 90% of it. So lets say it was done 6 | # in C+ only. 7 | # 8 | # With love, 9 | # P1kachu. 10 | 11 | QUIET=#-s 12 | ## COMPILER ## 13 | CXX = g++ 14 | 15 | ## FLAGS ## 16 | CXXFLAGS = -Wall -Wextra -Werror -pedantic -std=c++11 -I $(INCLDIR) 17 | CXXFLAGS += -pedantic -g 18 | CXXFLAGS += -Wundef -Wshadow -Wpointer-arith -Wcast-qual 19 | CXXFLAGS += -Wcast-align 20 | CXXFLAGS += -Wmissing-declarations 21 | CXXFLAGS += -Wunreachable-code 22 | CXXFLAGS += -fdiagnostics-color=always 23 | CXXFLAGS += -O3 24 | 25 | ## INCLUDES DIRECTORY ## 26 | INCLDIR = src/includes/ 27 | 28 | ## LIBS ## 29 | LDFLAGS = -lcapstone 30 | 31 | ## MAIN ## 32 | SRCS_1 = $(addsuffix .cc, $(addprefix src/level1/, strace syscalls mem_strace)) 33 | SRCS_1 += $(addsuffix .cc, $(addprefix src/helpers/, helpers)) 34 | 35 | SRCS_2 = $(addsuffix .cc, $(addprefix src/level4/, injector sanity_check)) 36 | SRCS_2 += $(addsuffix .cc, $(addprefix src/level3/, tracker)) 37 | SRCS_2 += $(addsuffix .cc, $(addprefix src/level2/, mem_strace_hook breaker dig_into_mem)) 38 | SRCS_2 += $(addsuffix .cc, $(addprefix src/level1/, strace syscalls)) 39 | SRCS_2 += $(addsuffix .cc, $(addprefix src/helpers/, helpers)) 40 | 41 | SRCS_3 = $(addsuffix .cc, $(addprefix src/level4/, injector sanity_check)) 42 | SRCS_3 += $(addsuffix .cc, $(addprefix src/level3/, mem_tracker tracker)) 43 | SRCS_3 += $(addsuffix .cc, $(addprefix src/level2/, breaker dig_into_mem)) 44 | SRCS_3 += $(addsuffix .cc, $(addprefix src/level1/, strace syscalls)) 45 | SRCS_3 += $(addsuffix .cc, $(addprefix src/helpers/, helpers)) 46 | 47 | SRCS_4 = $(addsuffix .cc, $(addprefix src/level4/, mem_checker injector sanity_check)) 48 | SRCS_4 += $(addsuffix .cc, $(addprefix src/level3/, tracker)) 49 | SRCS_4 += $(addsuffix .cc, $(addprefix src/level2/, breaker dig_into_mem)) 50 | SRCS_4 += $(addsuffix .cc, $(addprefix src/level1/, strace syscalls)) 51 | SRCS_4 += $(addsuffix .cc, $(addprefix src/helpers/, helpers)) 52 | 53 | ## EXEC NAME ## 54 | EXEC_1 = mem_strace 55 | EXEC_2 = mem_strace_hook 56 | EXEC_3 = mem_tracker 57 | EXEC_4 = mem_checker 58 | 59 | 60 | ############################################################################### 61 | ############################################################################### 62 | 63 | 64 | # Multi threaded make of the final binary # 65 | multi: 66 | $(MAKE) $(QUIET) -j all 67 | 68 | # Produce the final binary # 69 | all: libhooks debug 70 | $(MAKE) -B level1 71 | $(MAKE) -B level2 72 | $(MAKE) -B level3 73 | $(MAKE) -B level4 74 | clear 75 | 76 | # Produce Level4 binary 77 | level1: debug 78 | @echo -en "\033[31;1mCompiling level 1... " 79 | @$(CXX) $(CXXFLAGS) -D LEVEL=1 $(SRCS_1) $(LDFLAGS) -o $(EXEC_1) 80 | @echo -e "\033[32;1mDone.\033[0m" 81 | level2: debug 82 | @echo -en "\033[31;1mCompiling level 2... " 83 | @$(CXX) $(CXXFLAGS) -D LEVEL=2 $(SRCS_2) $(LDFLAGS) -o $(EXEC_2) 84 | @echo -e "\033[32;1mDone.\033[0m" 85 | level3: libhooks debug 86 | @echo -en "\033[31;1mCompiling level 3... " 87 | @$(CXX) $(CXXFLAGS) -D LEVEL=3 $(SRCS_3) $(LDFLAGS) -o $(EXEC_3) 88 | @echo -e "\033[32;1mDone.\033[0m" 89 | level4: libhooks debug 90 | @echo -en "\033[31;1mCompiling level 4... " 91 | @$(CXX) $(CXXFLAGS) -D LEVEL=4 $(SRCS_4) $(LDFLAGS) -o $(EXEC_4) 92 | @echo -e "\033[32;1mDone.\033[0m" 93 | 94 | 95 | # Produce debug binary # 96 | debug: 97 | # gcc ./tests/debug.cc -o c.o 98 | # nasm -f elf64 casm.asm -o casm.o 99 | # ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc casm.o c.o -o debug 100 | gcc -Wall -Wextra -Werror -I src/includes ./tests/debug.cc -o ./debug 101 | 102 | libhooks: 103 | gcc -Wall -Wextra -Werror -shared -I src/includes -fPIC -ldl src/level3/memory_hooks.c -o libhooks.so 104 | 105 | # Produce test binary, and launch # 106 | check: distclean libhooks debug multi 107 | clear 108 | @echo -e "\033[33;1m##################################################################################\ 109 | #######################\033[0m" 110 | @echo -e "\033[33;1m##################################################################################\ 111 | #######################\033[0m\n\n" 112 | ./$(EXEC_1) ./debug 2> /dev/null 113 | @echo -e "\033[33;1m##################################################################################\ 114 | #######################\033[0m\n\n" 115 | ./$(EXEC_2) ./debug 2> /dev/null 116 | @echo -e "\033[33;1m##################################################################################\ 117 | #######################\033[0m\n\n" 118 | ./$(EXEC_3) ./debug 2> /dev/null 119 | @echo -e "\033[33;1m##################################################################################\ 120 | #######################\033[0m\n\n" 121 | ./$(EXEC_3) --preload ./libhooks.so ./debug 2> /dev/null 122 | @echo -e "\033[33;1m##################################################################################\ 123 | #######################\033[0m\n\n" 124 | ./$(EXEC_4) --preload ./libhooks.so ./debug 125 | 126 | # Clean repository # 127 | 128 | clean: 129 | ./utils/repo_cleaner.sh 130 | 131 | distclean: clean 132 | $(RM) debug 133 | $(RM) $(EXEC_1) 134 | $(RM) $(EXEC_2) 135 | $(RM) $(EXEC_3) 136 | $(RM) $(EXEC_4) 137 | $(RM) libhooks 138 | $(RM) WHATEVER 139 | 140 | .PHONY: multi all clean bonus libhook debug distclean 141 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ================================================================================= 2 | LSE RECRUITEMENT 2016 - MY MEMCHECK 3 | ================================================================================= 4 | 5 | Goal: Reimplement a memory checker, A.K.A look for invalid (out of bounds) memory 6 | accesses, and memory leaks 7 | https://lse.epita.fr/data/sujets/sujet-my_mmck.pdf 8 | 9 | 10 | Level1 - Strace: 11 | src/level1 12 | Use of ptrace to track system calls from a traced program 13 | Prints arguments for execve, fork, vfork, clone, exit, 14 | exit_group, brk, mmap, munmap, mremap, mprotect 15 | Catches return code with edi (exit*) 16 | prints PID and where the error was raised 17 | Prints other functions names, but no arguments 18 | 19 | Level2 - Hooked Strace: 20 | src/level2 21 | #include Level1 22 | - PTRACE_SYSCALL 23 | + HOOK ON SYSCALLS 24 | 1 line against 700 less effective but hey, WE rule the stuff now 25 | 26 | Level3 - Memory tracer: 27 | src/level3 28 | #include Level{1,2} 29 | Hooks on frequently used function that play with memory 30 | malloc, realloc, free, calloc 31 | Save each allocated area, to check if we are not out of bound 32 | Pretty neat 33 | memory_hook.c generates libhooks.so. Preloaded, it allows memcheck 34 | to trace malloc style functions 35 | 36 | Level4 - Memory checker: 37 | src/level4 38 | #include Level{1...3} 39 | Remove memory access 40 | Handle segfaults from that 41 | display memory leaks and invalid accesses from mmap 42 | display memory leaks and invalid accesses now from malloc 43 | 44 | 45 | 46 | What works: 47 | * Catch, print, and run child's syscall 48 | * Maintain a map of every allocated memory from the child, 49 | from mmap and malloc 50 | * Syscalls from loaded libraries too 51 | * Display memory leaks from mmap and malloc (tells if on the heap or not) 52 | * Display invalid accesses from mmap and now malloc (RW) 53 | * Handles segfaults too, skip them and continue 54 | * Get instructions sizes (access) 55 | 56 | 57 | What doesn't: 58 | * Differentiates reads from writes 100% of the time 59 | * Handle invalid frees 60 | 61 | 62 | 63 | NOTES: 64 | 65 | XIP, XAX, and other variables like this were an attempt at preparing for a 66 | *possible* multi-arch compliant program. They translate to rip or eip (in the 67 | case of XIP for example) regarding the machine. 68 | 69 | ├── src 70 | │   ├── helpers 71 | │   │   └── helpers.cc // Some helper functions used in the project 72 | │   ├── includes 73 | │   │   ├── colors.hh // Pretty printing helper 74 | │   │   ├── defines.hh // Every included header, macro, and some class 75 | │   │   ├── helpers.hh 76 | │   │   ├── level1.hh 77 | │   │   ├── level2.hh 78 | │   │   ├── level4.hh 79 | │   │   ├── shared.hh // Shared data between memcheck and libhooks.so 80 | │   │   └── syscalls.hh 81 | │   ├── level1 // Basic strace implementation 82 | │   │   ├── mem_strace.cc 83 | │   │   ├── strace.cc 84 | │   │   └── syscalls.cc 85 | │   ├── level2 // Strace without PTRACE_SYSCALLS 86 | │   │   ├── breaker.cc 87 | │   │   ├── dig_into_mem.cc 88 | │   │   └── mem_strace_hook.cc 89 | │   ├── level3 // Memory tracker 90 | │   │   ├── memory_hooks.c // Generates libhooks.so 91 | │   │   ├── mem_tracker.cc 92 | │   │   └── tracker.cc 93 | │   └── level4 // Inject code, and check memory access 94 | │   ├── injector.cc 95 | │   ├── mem_checker.cc 96 | │   └── sanity_check.cc 97 | ├── tests // Quick tests 98 | │   └── debug.cc -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | ================================================== 2 | # TODO # 3 | ================================================== 4 | Redo this project entirely when not busy [ ] 5 | Print filenames from execve correctly [/] 6 | Print PROT with & [ ] 7 | Delete NOMO tags [X] 8 | Follow_fork_mode (multi threading) [ ] 9 | Get rid of reinterpret_casts [/] 10 | Find Ehdr [X] 11 | Handle vdso [X] 12 | Refactor child digging [/] 13 | Challenge: Some asm inlining [X] 14 | Handle ld.so [X] 15 | find why it sucks with my preloaded library [X] 16 | Fix free printing shitty length [X] 17 | do PTHL :( [X] 18 | Handle half freeing with munmap [X] 19 | Differentiate writes from reads [/] 20 | repair level3 again... [X] 21 | Suppress memory checks on syscalls [X] 22 | Find the fucking way malloc accesses memory [X] 23 | Check for invalid frees/unmap [/] 24 | Create some tests [/] 25 | Handle when a null pointer is given to realloc [ ] 26 | Handle access sizes [X] 27 | Error handling [/] 28 | Remove debug printing [ ] 29 | repair level2 [X] 30 | -------------------------------------------------------------------------------- /src/helpers/helpers.cc: -------------------------------------------------------------------------------- 1 | #include "helpers.hh" 2 | 3 | bool binary_exists(const std::string& name) 4 | { 5 | return (access(name.c_str(), F_OK) != -1); 6 | } 7 | 8 | bool is_elf(Elf64_Ehdr* hdr) 9 | { 10 | return hdr->e_ident[EI_MAG0] == ELFMAG0 11 | && hdr->e_ident[EI_MAG1] == ELFMAG1 12 | && hdr->e_ident[EI_MAG2] == ELFMAG2 13 | && hdr->e_ident[EI_MAG3] == ELFMAG3; 14 | } 15 | 16 | char* get_cmd_opt(char** begin, char** end, const std::string& option) 17 | { 18 | char** itr = std::find(begin, end, option); 19 | 20 | if (itr != end &&++itr != end) 21 | return* itr; 22 | return NULL; 23 | } 24 | 25 | bool cmd_opt_exists(char** begin, char** end, const std::string& option) 26 | { 27 | return std::find(begin, end, option) != end; 28 | } 29 | 30 | void lvl3_print_brk(int prefix, void* origin_break, void* actual_break) 31 | { 32 | if (!prefix) 33 | { 34 | long len = origin_break ? 35 | (char*)actual_break - (char*)origin_break : 0; 36 | fprintf(OUT, "brk { addr = %p, len = 0x%lx, prot = 3 }\n", 37 | (void*)actual_break, len); 38 | } 39 | else 40 | { 41 | long len = origin_break ? 42 | (char*)actual_break - (char*)origin_break : 0; 43 | fprintf(OUT, "|_ to { addr = %p, len = 0x%lx, prot = 3 }\n", 44 | (void*)actual_break, len); 45 | 46 | } 47 | } 48 | 49 | void lvl3_print_mremap(int prefix, long addr, long len, int prot) 50 | { 51 | if (!prefix) 52 | fprintf(OUT, "mremap { addr = %p, len = 0x%lx, prot = %d }\n", 53 | (void*)addr, len, prot); 54 | else 55 | fprintf(OUT, "|_ to { addr = %p, len = 0x%lx, prot = %d }\n", 56 | (void*)addr, len, prot); 57 | } 58 | 59 | void lvl3_print_mprotect(int prefix, long addr, long len, int prot) 60 | { 61 | if (!prefix) 62 | fprintf(OUT, "mprotect { addr = %p, len = 0x%lx, prot = %d }\n", 63 | (void*)addr, len, prot); 64 | else 65 | fprintf(OUT, "|_ to { addr = %p, len = 0x%lx, prot = %d }\n", 66 | (void*)addr, len, prot); 67 | } 68 | 69 | void lvl3_print_realloc(int prefix, long from, long to, long len) 70 | { 71 | if (!prefix) 72 | fprintf(OUT, "realloc { addr = %p, len = 0x%lx }\n", 73 | (void*)from, len); 74 | else 75 | fprintf(OUT, "|_ to { addr = %p, len = 0x%lx }\n", 76 | (void*)to, len); 77 | } 78 | -------------------------------------------------------------------------------- /src/includes/colors.hh: -------------------------------------------------------------------------------- 1 | #ifndef COLORS_HH 2 | # define COLORS_HH 3 | 4 | // Special attributes 5 | #define BOLD "\033[1m" 6 | #define UND "\033[4m" 7 | #define BLINK "\033[5m" 8 | #define INV "\033[7m" 9 | #define NONE "\033[0m" 10 | 11 | // Colors 12 | #define BLACK "\033[30;1m" 13 | #define RED "\033[31;1m" 14 | #define PRED "\033[31m" 15 | #define GREEN "\033[32;1m" 16 | #define YELLOW "\033[33;1m" 17 | #define BLUE "\033[34;1m" 18 | #define MAGENTA "\033[35;1m" 19 | #define CYAN "\033[36;1m" 20 | #define L_GRAY "\033[37;1m" 21 | #define D_GRAY "\033[90;1m" 22 | #define L_RED "\033[91;1m" 23 | #define L_GREEN "\033[92;1m" 24 | #define L_YELLOW "\033[93;1m" 25 | #define L_BLUE "\033[94;1m" 26 | #define L_MAGENTA "\033[95;1m" 27 | #define L_CYAN "\033[96;1m" 28 | #define WHITE "\033[97;1m" 29 | #define C_END "\033[97;1m" 30 | 31 | // Backgrounds 32 | #define B_BLACK "\033[40m" 33 | #define B_RED "\033[41m" 34 | #define B_GREEN "\033[42m" 35 | #define B_YELLOW "\033[43m" 36 | #define B_BLUE "\033[44m" 37 | #define B_MAGENTA "\033[45m" 38 | #define B_CYAN "\033[46m" 39 | #define B_L_GRAY "\033[47m" 40 | #define B_D_GRAY "\033[100m" 41 | #define B_L_RED "\033[101m" 42 | #define B_L_GREEN "\033[102m" 43 | #define B_L_YELLOW "\033[103m" 44 | #define B_L_BLUE "\033[104m" 45 | #define B_L_MAGENTA "\033[105m" 46 | #define B_L_CYAN "\033[106m" 47 | #define B_WHITE "\033[107m" 48 | #define B_END "\033[49m" 49 | 50 | #endif /* !COLORS_HH */ 51 | -------------------------------------------------------------------------------- /src/includes/defines.hh: -------------------------------------------------------------------------------- 1 | #ifndef DEFINES_HH 2 | # define DEFINES_HH 3 | 4 | /* 5 | ** Dirty to look at but easier to manage at the end 6 | ** Every define should be here 7 | ** Classes are here when I have no choice (else 8 | ** I would have put them into their own header) 9 | */ 10 | 11 | 12 | /* Includes for the whole project */ 13 | # include 14 | # include 15 | # include 16 | # include 17 | # include 18 | # include 19 | # include 20 | # include 21 | # include 22 | 23 | # include 24 | # include 25 | # include 26 | # include 27 | # include 28 | # include 29 | # include 30 | # include 31 | # include 32 | # include 33 | # include 34 | # include 35 | # include 36 | # include 37 | # include 38 | # include 39 | # include 40 | # include 41 | 42 | # include "colors.hh" 43 | # include "shared.hh" 44 | # include "helpers.hh" 45 | # include "syscalls.hh" 46 | 47 | /* Macros */ 48 | # define OUT stdout 49 | # define VERSION "v1.0" 50 | # define MAIN_CHILD "origins" 51 | # define NULL_STRING "NULL" 52 | # define CUSTOM_BREAKPOINT -3 53 | # define SYSCALL_ERROR -2 54 | # define NO_SYSCALL -1 55 | # define BONUS 1 56 | # define MMAP_SYSCALL 9 57 | # define MPROTECT_SYSCALL 10 58 | # define MUNMAP_SYSCALL 11 59 | # define BRK_SYSCALL 12 60 | # define MREMAP_SYSCALL 25 61 | # define CLONE_SYSCALL 56 62 | # define FORK_SYSCALL 57 63 | # define VFORK_SYSCALL 58 64 | # define EXECVE_SYSCALL 59 65 | # define EXIT_SYSCALL 60 66 | # define EXIT_GROUP_SYSCALL 231 67 | # define MAX_STRING_SIZE 255 68 | # define NOT_FOUND 404 69 | # define TRAP_LEN 1 70 | # define TRAP_INST 0xCC 71 | # define SYSCALL 0x050f 72 | # define SEGFAULT 0xc0ca 73 | # define MALLOC_CHILD 0xdeadbeef 74 | # define MALLOC_STUFF_ADDRESS 0x700000000000 75 | # define BONUS 1 76 | 77 | # if defined(__i386) 78 | 79 | # define INSTR_REG EIP 80 | # define XAX eax 81 | # define XIP eip 82 | # define O_XAX ORIG_EAX 83 | # define P_XAX EAX 84 | # define TRAP_MASK 0xFFFFFF00 85 | 86 | # elif defined(__x86_64) 87 | 88 | # define INSTR_REG RIP 89 | # define XAX rax 90 | # define XIP rip 91 | # define O_XAX ORIG_RAX 92 | # define P_XAX RAX 93 | # define TRAP_MASK 0xFFFFFFFFFFFFFF00 94 | 95 | # endif /* !ARCH */ 96 | 97 | 98 | /* "Functions" */ 99 | # define UNUSED(x) { (x) = (x); } 100 | # define print_errno() \ 101 | { \ 102 | if (errno) \ 103 | { \ 104 | fprintf(OUT, "%sERROR%s Something went wrong: %s (%s%s%s:%d)\n", \ 105 | RED, NONE, strerror(errno), RED, __FILE__, NONE, __LINE__); \ 106 | exit(-1); \ 107 | } \ 108 | } 109 | 110 | # define get_orig_xax(pid) { ptrace(PTRACE_PEEKUSER, pid, sizeof (long) * O_XAX) } 111 | # define get_xax(pid) { ptrace(PTRACE_PEEKUSER, pid, sizeof (long) * P_XAX) } 112 | # define get_xip(pid) { ptrace(PTRACE_PEEKUSER, pid, sizeof (long) * INSTR_REG) } 113 | # define void_of(number) { reinterpret_cast(number) } 114 | # define ANCHOR(x) fprintf(OUT, "\033[3%d;1mANCHOR #%lx\033[0m\n", x % 7, (unsigned long)x) 115 | # define PID(pid) fprintf(OUT, "[%d]\n", pid) 116 | 117 | /* Thank you circular dependencies... */ 118 | class Tracker; 119 | 120 | class Breaker 121 | { 122 | public: 123 | Breaker(std::string binary_name, pid_t pid); 124 | 125 | // Get the child r_debug struct address 126 | struct r_debug* get_r_debug(pid_t pid); 127 | 128 | // Remove a breakpoint from the child in the r region 129 | void remove_breakpoint(std::string r, void* addr); 130 | 131 | // Add a breakpoint at addr in the r region 132 | void add_breakpoint(std::string r, void* addr); 133 | 134 | // Find and patch every syscall from the child 135 | ssize_t find_syscalls(void* addr); 136 | 137 | // Is the breakpoint in the Breaker map (I hope it is) 138 | char is_from_us(void* addr) const; 139 | 140 | // Remove a breakpoint, handle the syscall, singlestep, re add the breakpoint 141 | long handle_bp(void* addr, bool p, Tracker& t); 142 | long handle_bp(void* addr, bool p); 143 | long exec_breakpoint(std::string r, void* addr, bool p); 144 | long exec_breakpoint(std::string r, void* addr, bool p, Tracker& t); 145 | 146 | // For debugging purposes 147 | void print_bps() const; 148 | 149 | // When new library are loaded, may not correctly work 150 | void reset_libs(void* link_map); 151 | 152 | // Vars 153 | std::map> handled_syscalls; 154 | void* rr_brk; 155 | pid_t pid; 156 | struct r_debug* r_deb; 157 | std::string name; 158 | void* program_entry_point; 159 | }; 160 | 161 | class Mapped 162 | { 163 | public: 164 | Mapped(unsigned long b, unsigned long len, unsigned long prot, int id_inc) 165 | { 166 | mapped_begin = b; 167 | mapped_length = len; 168 | mapped_protections = prot; 169 | executable_bit = prot & PROT_EXEC; 170 | id = id_inc; 171 | } 172 | 173 | // Does the mapped page contains this address ? 174 | bool area_contains(unsigned long addr) const; 175 | 176 | unsigned long mapped_begin; // Page bagin address 177 | unsigned long mapped_length; // Page original length 178 | unsigned long mapped_protections; // Page original protections 179 | int executable_bit; // Was the page executable 180 | int id; // For debug purposes 181 | }; 182 | 183 | 184 | class Tracker 185 | { 186 | public: 187 | Tracker(std::string binary_name, pid_t child) 188 | { 189 | pid = child; 190 | name = binary_name; 191 | origin_program_break = 0; 192 | actual_program_break = 0; 193 | id_inc = 0; 194 | nb_of_frees = 0; 195 | nb_of_allocs = 0; 196 | } 197 | 198 | 199 | // Is the syscall one of those we patched 200 | bool of_interest(int syscall) const; 201 | 202 | // For debugging purposes 203 | void print_mapped_areas() const; 204 | 205 | // Different handler regarding the raised syscall 206 | int handle_brk(Breaker& b, void* bp, bool print); 207 | int handle_munmap(Breaker& b, void* bp, bool print); 208 | int handle_mmap(Breaker& b, void* bp, bool print); 209 | int handle_syscall(int syscall, Breaker& b, void* bp, bool print); 210 | int handle_mprotect(Breaker& b, void* bp, bool print); 211 | int handle_mremap(Breaker& b, void* bp, bool print); 212 | 213 | // Handles for the hooked functions 214 | int custom_alloc(int prefix, Breaker& b, void* bp, bool print); 215 | int custom_free(Breaker& b, void* bp, bool print); 216 | int custom_realloc(Breaker& b, void* bp, bool print); 217 | 218 | // Remove a page from the Tracker 219 | bool remove_mapped(void* addr, long len); 220 | 221 | // Get an iterator on the mapped page that contains the addr 222 | // Else returns mapped_areas.end() 223 | std::list::iterator get_mapped(unsigned long addr); 224 | 225 | // Used to remove splitted pages 226 | void tail_remove(std::list::iterator it, int iteration); 227 | 228 | std::list mapped_areas; 229 | std::string name; 230 | pid_t pid; 231 | void* actual_program_break; 232 | void* origin_program_break; 233 | int id_inc; // For debugging purposes 234 | int nb_of_frees; 235 | int nb_of_allocs; 236 | }; 237 | 238 | #endif /* !DEFINES_HH */ 239 | -------------------------------------------------------------------------------- /src/includes/helpers.hh: -------------------------------------------------------------------------------- 1 | #ifndef HELPERS_HH 2 | # define HELPERS_HH 3 | 4 | # include "defines.hh" 5 | 6 | // Return true if the binary is accessible 7 | bool binary_exists(const std::string& name); 8 | 9 | // Check the magic numbers 10 | bool is_elf(Elf64_Ehdr* hdr); 11 | 12 | // Used to parse cmd line options 13 | char* get_cmd_opt(char** begin, char** end, const std::string& option); 14 | bool cmd_opt_exists(char** begin, char** end, const std::string& option); 15 | 16 | // Printers for level3 17 | void lvl3_print_brk(int prefix, void* origin_break, void* actual_break); 18 | void lvl3_print_mremap(int prefix, long addr, long len, int prot); 19 | void lvl3_print_mprotect(int prefix, long addr, long len, int prot); 20 | void lvl3_print_realloc(int prefix, long from, long to, long len); 21 | #endif /* !HELPERS_HH */ 22 | -------------------------------------------------------------------------------- /src/includes/level1.hh: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL1_HH 2 | # define LEVEL1_HH 3 | 4 | # include "defines.hh" 5 | 6 | int run_child(int argc, char** argv, char* ld_preload); 7 | int trace_child(pid_t child); 8 | 9 | #endif /* LEVEL1_HH */ 10 | -------------------------------------------------------------------------------- /src/includes/level2.hh: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL2_HH 2 | # define LEVEL2_HH 3 | 4 | # include "defines.hh" 5 | 6 | // Recover r_brk address 7 | void* get_r_brk(void* rr_debug, pid_t pid_child); 8 | 9 | // Get pt_dynamic region to get r_debug 10 | void* get_pt_dynamic(unsigned long phent, unsigned long phnum, pid_t pid, void* at_phdr); 11 | 12 | // Wrapper to get r_debug 13 | void* get_final_r_debug(Elf64_Dyn* dt_struct, pid_t pid_child); 14 | 15 | // Get program header 16 | void* get_phdr(unsigned long& phent, unsigned long& phnum, pid_t pid_child); 17 | 18 | // Recover elf link map address 19 | void* get_link_map(void* rr_debug, pid_t pid, int* status); 20 | 21 | // At first, used to print a string from the child. 22 | // Now just returns a pointer to a copy of the latter. 23 | void* print_string_from_mem(void* str, pid_t pid); 24 | 25 | // Inject breakpoints into dynamically allocated memory 26 | void browse_link_map(void* link_m, pid_t pid, Breaker* b); 27 | 28 | // Check for syscalls 29 | int disass(const char* name, void* phdr, long len, Breaker& b, pid_t pid); 30 | 31 | // Recover offsets from sections in ELF 32 | std::pairget_sections(const char* lib_name, Breaker& b); 33 | 34 | #endif /* !LEVEL2_HH */ 35 | -------------------------------------------------------------------------------- /src/includes/level4.hh: -------------------------------------------------------------------------------- 1 | #ifndef LEVEL4_HH 2 | # define LEVEL4_HH 3 | 4 | # include "level2.hh" 5 | # include "defines.hh" 6 | 7 | // Play with pages PROT_* flags, to break everything \o/ 8 | int remove_page_protection(pid_t pid, Tracker& t); 9 | int set_page_protection(unsigned long addr, size_t len, 10 | unsigned long prot, pid_t pid); 11 | int reset_page_protection(pid_t pid, Tracker& t); 12 | 13 | // Catch segfaults and breakpoints, then call previous levels 14 | int handle_injected_sigsegv(pid_t pid, Tracker& t); 15 | int handle_injected_syscall(int syscall, Breaker& b, void* bp, Tracker& t); 16 | 17 | // Check for invalid memory accesses 18 | int sanity_customs(pid_t pid, Tracker& t, int handler); 19 | int display_memory_leaks(Tracker& t); 20 | int invalid_free(pid_t pid, void* pointer, Tracker& t); 21 | #endif /* LEVEL4_HH */ 22 | -------------------------------------------------------------------------------- /src/includes/shared.hh: -------------------------------------------------------------------------------- 1 | #ifndef SHARED_HH 2 | # define SHARED_HH 3 | 4 | # define CUSTOM_SYSCALL_MALLOC 0xa110c8 5 | # define CUSTOM_SYSCALL_REALLOC 0xc0ffee 6 | # define CUSTOM_SYSCALL_CALLOC 0xdec0de 7 | # define CUSTOM_SYSCALL_FREE 0xb0bca7 8 | 9 | #endif /* SHARED_HH */ 10 | -------------------------------------------------------------------------------- /src/includes/syscalls.hh: -------------------------------------------------------------------------------- 1 | #ifndef SYSCALLS_HH 2 | # define SYSCALLS_HH 3 | 4 | # include "defines.hh" 5 | 6 | // Well... I should stop documenting the obvious 7 | int print_retval(pid_t child, int syscall); 8 | int print_syscall(pid_t child, int orig); 9 | 10 | #endif /* !SYSCALLS_HH */ 11 | -------------------------------------------------------------------------------- /src/level1/mem_strace.cc: -------------------------------------------------------------------------------- 1 | #include "level1.hh" 2 | 3 | int main(int argc, char** argv) 4 | { 5 | if (argc < 2) 6 | { 7 | fprintf(OUT, 8 | "Usage: %s binary_to_trace[ARGS]\n", 9 | argv[0]); 10 | return 0; 11 | } 12 | 13 | std::string name = argv[1]; 14 | 15 | if (!binary_exists(name) && name.find("./") != std::string::npos) 16 | { 17 | // Binary not present 18 | fprintf(OUT, 19 | "%sERROR:%s Binary %s not found.\n", 20 | RED, NONE, name.c_str()); 21 | exit(-1); 22 | } 23 | 24 | pid_t pid = 0; 25 | 26 | if ((pid = fork()) == 0) 27 | return run_child(argc - 1, argv + 1, NULL); 28 | 29 | return trace_child(pid); 30 | } 31 | -------------------------------------------------------------------------------- /src/level1/strace.cc: -------------------------------------------------------------------------------- 1 | #include "level1.hh" 2 | #include "syscalls.hh" 3 | 4 | static int wait_for_syscall(pid_t child) 5 | { 6 | int status = 0; 7 | while (true) 8 | { 9 | // Trace system calls from child 10 | if (ptrace(PTRACE_SYSCALL, child, 0, 0) == -1) 11 | fprintf(OUT, 12 | "%sERROR:%s PTRACE_SYSCALL failed\n", 13 | RED, NONE); 14 | 15 | // pid, status, options 16 | waitpid(child, &status, __WALL); 17 | 18 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) 19 | return fprintf(OUT, 20 | "[%d] Signal 11 caught (SIGSEGV)\n", 21 | child); 22 | 23 | 24 | 25 | // Program was stopped by a syscall 26 | if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) 27 | return 0; 28 | 29 | // Program exited normally 30 | if (WIFEXITED(status)) 31 | return 1; 32 | } 33 | } 34 | 35 | 36 | int run_child(int argc, char** argv, char* ld_preload) 37 | { 38 | char** args = new char* [argc + 1]; 39 | memcpy(args, argv, argc * sizeof (char*)); 40 | args[argc] = nullptr; 41 | 42 | if (ptrace(PTRACE_TRACEME) == -1) 43 | fprintf(OUT, 44 | "%sERROR:%s PTRACE_TRACEME failed\n", 45 | RED, NONE); 46 | 47 | int ret = 0; 48 | if (ld_preload) 49 | { 50 | std::stringstream ss; 51 | ss << "LD_PRELOAD=" << ld_preload; 52 | std::string s = ss.str(); 53 | 54 | char* tmp = strdup(s.c_str()); 55 | char* const envs[] = { tmp, NULL }; 56 | ret = execve(args[2], args + 2, envs); 57 | print_errno(); 58 | free(tmp); 59 | delete[] args; 60 | } 61 | else 62 | { 63 | ret = execvp(args[0], args); 64 | delete[] args; 65 | } 66 | return ret; 67 | } 68 | 69 | 70 | int trace_child(pid_t child) 71 | { 72 | int status = 0; 73 | int retval = 0; 74 | waitpid(child, &status, 0); 75 | 76 | // PTRACE_O_TRACESYSGOOD is used to 77 | // differentiate syscalls from normal traps 78 | if (ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD)) 79 | fprintf(OUT, 80 | "%sERROR:%s PTRACE_O_TRACESYSGOOD failed\n", 81 | RED, NONE); 82 | 83 | while (true) 84 | { 85 | 86 | if (wait_for_syscall(child)) 87 | break; 88 | 89 | // Retrieve data from $rax 90 | long syscall = get_orig_xax(child); 91 | if (syscall < 0) 92 | continue; 93 | 94 | int rdi = print_syscall(child, syscall); 95 | 96 | int tmp = wait_for_syscall(child); 97 | 98 | retval = print_retval(child, syscall); 99 | 100 | if (syscall == EXIT_SYSCALL || syscall == EXIT_GROUP_SYSCALL) 101 | retval = rdi; 102 | 103 | if (tmp) 104 | break; 105 | 106 | } 107 | 108 | fprintf(OUT, "\n+++ Process %d exited with %d+++\n", child, retval); 109 | return 0; 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/level1/syscalls.cc: -------------------------------------------------------------------------------- 1 | #include "syscalls.hh" 2 | 3 | static void print_syscall_name(int id) 4 | { 5 | if(id < 0) 6 | return; 7 | std::ifstream in("/usr/include/asm/unistd_64.h"); 8 | std::string s; 9 | id+=3; 10 | for (int i = 0; i < id; ++i) 11 | getline(in, s); 12 | 13 | getline(in, s); 14 | s = s.substr(s.find("__NR_") + 5, strlen(s.c_str())); 15 | s = s.substr(0, s.find(" ")); 16 | 17 | fprintf(OUT, "%s(...", s.c_str()); 18 | } 19 | 20 | int print_retval(pid_t child, int syscall) 21 | { 22 | long retval = ptrace(PTRACE_PEEKUSER, child, sizeof (long) * RAX); 23 | 24 | if (retval >= 0 || errno == 0) 25 | { 26 | switch (syscall) 27 | { 28 | case MMAP_SYSCALL: 29 | case MREMAP_SYSCALL: 30 | case BRK_SYSCALL: 31 | fprintf(OUT, ") = 0x%lx\n", retval); 32 | break; 33 | default: 34 | fprintf(OUT, ") = %ld\n", retval); 35 | break; 36 | } 37 | } 38 | else 39 | fprintf(OUT, ") = ?\n"); 40 | 41 | return retval; 42 | 43 | } 44 | 45 | static void print_addresses(pid_t child, user_regs_struct& regs) 46 | { 47 | #if BONUS 48 | fprintf(OUT, "[%d][0x%08llx] ", child, regs.rip); 49 | #else 50 | UNUSED(child); 51 | UNUSED(regs); 52 | #endif 53 | } 54 | 55 | static void print_mmap(pid_t child, user_regs_struct& regs) 56 | { 57 | 58 | print_addresses(child, regs); 59 | fprintf(OUT, "mmap("); 60 | 61 | #if BONUS 62 | char buffer[128]; 63 | 64 | sprintf(buffer, 65 | "addr = 0x%llx, len = %lld, ", regs.rdi, regs.rsi); 66 | sprintf(buffer + strlen(buffer), 67 | "prot = %lld, flags = %lld, ", regs.rdx, regs.r10); 68 | sprintf(buffer + strlen(buffer), 69 | "fd = %d, off = 0x%llx", static_cast(regs.r8), regs.r9); 70 | 71 | fprintf(OUT, buffer); 72 | #endif 73 | } 74 | 75 | static void print_mprotect(pid_t child, user_regs_struct& regs) 76 | { 77 | print_addresses(child, regs); 78 | fprintf(OUT, "mprotect("); 79 | 80 | #if BONUS 81 | char buffer[128]; 82 | sprintf(buffer, 83 | "addr = 0x%llx, len = %lu, ", 84 | regs.rdi, static_cast(regs.rsi)); 85 | 86 | sprintf(buffer + strlen(buffer), 87 | "prot = %lld", 88 | regs.rdx); 89 | fprintf(OUT, buffer); 90 | #endif 91 | } 92 | 93 | static void print_munmap(pid_t child, user_regs_struct& regs) 94 | { 95 | print_addresses(child, regs); 96 | fprintf(OUT, "munmap("); 97 | 98 | #if BONUS 99 | char buffer[128]; 100 | sprintf(buffer, 101 | "addr = 0x%llx, len = %lu", 102 | regs.rdi, static_cast(regs.rsi)); 103 | fprintf(OUT, buffer); 104 | #endif 105 | } 106 | 107 | static void print_brk(pid_t child, user_regs_struct& regs) 108 | { 109 | print_addresses(child, regs); 110 | fprintf(OUT, "brk("); 111 | 112 | #if BONUS 113 | char buffer[128]; 114 | sprintf(buffer, "addr = 0x%llx", regs.rdi); 115 | fprintf(OUT, buffer); 116 | #endif 117 | } 118 | 119 | static void print_mremap(pid_t child, user_regs_struct& regs) 120 | { 121 | print_addresses(child, regs); 122 | fprintf(OUT, "mremap("); 123 | 124 | #if BONUS 125 | char buffer[128]; 126 | 127 | sprintf(buffer, 128 | "old_addr = 0x%llx, old_size = %lu, ", 129 | regs.rdi, static_cast(regs.rsi)); 130 | 131 | sprintf(buffer + strlen(buffer), 132 | "new_size = %lu, flags = %lld, ", 133 | static_cast(regs.rdx), regs.r10); 134 | 135 | sprintf(buffer + strlen(buffer), 136 | "[new_addr = 0x%llx]", 137 | regs.r8); 138 | 139 | fprintf(OUT, buffer); 140 | #endif 141 | } 142 | 143 | static void print_clone(pid_t child, user_regs_struct& regs) 144 | { 145 | print_addresses(child, regs); 146 | fprintf(OUT, "clone("); 147 | 148 | #if BONUS 149 | char b[128]; 150 | sprintf(b, "clone_flags = 0x%llx, newsp = %lld, ", 151 | regs.rdi, regs.rsi); 152 | 153 | sprintf(b + strlen(b), "parent_tidptr = %llx, child_tidpte = %llx, ", 154 | regs.rdx, regs.r10); 155 | 156 | sprintf(b + strlen(b), "tls_val = %d", static_cast(regs.r8)); 157 | 158 | fprintf(OUT, b); 159 | #endif 160 | } 161 | 162 | static void print_fork(pid_t child, user_regs_struct& regs) 163 | { 164 | print_addresses(child, regs); 165 | fprintf(OUT, "fork("); 166 | } 167 | 168 | static void print_vfork(pid_t child, user_regs_struct& regs) 169 | { 170 | print_addresses(child, regs); 171 | fprintf(OUT, "vfork("); 172 | } 173 | 174 | static void print_execve(pid_t child, user_regs_struct& regs) 175 | { 176 | print_addresses(child, regs); 177 | fprintf(OUT, "execve("); 178 | 179 | #if BONUS 180 | char b[128]; 181 | char* str = reinterpret_cast(regs.rdi); 182 | 183 | // FIXME : Correct output name in execve 184 | // After doing the other levels, I saw that I would 185 | // have to do too much stuff for quite nothing 186 | // So junk is good 187 | sprintf(b, "filename = %s, argv = %p, ", 188 | str ? str : "NULL", reinterpret_cast(regs.rsi)); 189 | sprintf(b + strlen(b), "envp = %p", reinterpret_cast(regs.rdx)); 190 | 191 | fprintf(OUT, b); 192 | #endif 193 | } 194 | 195 | static int print_exit(pid_t child, user_regs_struct& regs) 196 | { 197 | print_addresses(child, regs); 198 | fprintf(OUT, "exit("); 199 | 200 | #if BONUS 201 | fprintf(OUT, "%d", static_cast(regs.rdi)); 202 | #endif 203 | 204 | return static_cast(regs.rdi); 205 | } 206 | 207 | static int print_exitgroup(pid_t child, user_regs_struct& regs) 208 | { 209 | print_addresses(child, regs); 210 | fprintf(OUT, "exit_group("); 211 | 212 | #if BONUS 213 | fprintf(OUT, "%d", static_cast(regs.rdi)); 214 | #endif 215 | 216 | return static_cast(regs.rdi); 217 | } 218 | 219 | static void print_lambda(pid_t child, int orig, user_regs_struct& regs) 220 | { 221 | print_addresses(child, regs); 222 | 223 | print_syscall_name(orig); 224 | } 225 | 226 | 227 | int print_syscall(pid_t child, int orig) 228 | { 229 | struct user_regs_struct regs; 230 | 231 | // Get child register and store them into regs 232 | ptrace(PTRACE_GETREGS, child, NULL, ®s); 233 | 234 | switch (orig) 235 | { 236 | case -1: 237 | return 0; 238 | case MMAP_SYSCALL: // mmap 239 | print_mmap(child, regs); 240 | break; 241 | 242 | case MPROTECT_SYSCALL: // mprotect 243 | print_mprotect(child, regs); 244 | break; 245 | 246 | case MUNMAP_SYSCALL: // munmap 247 | print_munmap(child, regs); 248 | break; 249 | 250 | case BRK_SYSCALL: // brk 251 | print_brk(child, regs); 252 | break; 253 | 254 | case MREMAP_SYSCALL: // mremap 255 | print_mremap(child, regs); 256 | break; 257 | 258 | case CLONE_SYSCALL: // clone 259 | print_clone(child, regs); 260 | break; 261 | 262 | case FORK_SYSCALL: // fork 263 | print_fork(child, regs); 264 | break; 265 | 266 | case VFORK_SYSCALL: // vfork 267 | print_vfork(child, regs); 268 | break; 269 | 270 | case EXECVE_SYSCALL: // execve 271 | print_execve(child, regs); 272 | break; 273 | 274 | case EXIT_SYSCALL: // exit 275 | return print_exit(child, regs); 276 | 277 | case EXIT_GROUP_SYSCALL: // exit_group 278 | return print_exitgroup(child, regs); 279 | 280 | default: // don't care 281 | print_lambda(child, orig, regs); 282 | break; 283 | } 284 | 285 | return 0; 286 | } 287 | -------------------------------------------------------------------------------- /src/level2/breaker.cc: -------------------------------------------------------------------------------- 1 | #include "level4.hh" 2 | 3 | struct r_debug* Breaker::get_r_debug(pid_t pid_child) 4 | { 5 | Elf64_Dyn* dt_struct = NULL; 6 | unsigned long at_phent = 0; 7 | unsigned long at_phnum = 0; 8 | 9 | 10 | // Get interesting Phdr 11 | void* at_phdr = get_phdr(at_phent, at_phnum, pid_child); 12 | 13 | print_errno(); 14 | 15 | // Something went wrong ? 16 | if (!at_phdr) 17 | return NULL; 18 | 19 | 20 | // FIXME : Check if ELF ? Get Ehdr, helpers/is_elf 21 | 22 | // Get PT_DYNAMIC entry 23 | dt_struct = (Elf64_Dyn*)get_pt_dynamic(at_phent, at_phnum, 24 | pid_child, at_phdr); 25 | 26 | 27 | // Get r_debug address 28 | void* rr_debug = get_final_r_debug(dt_struct, pid_child); 29 | 30 | 31 | // Get r_debug content 32 | rr_brk = get_r_brk(rr_debug, pid_child); 33 | 34 | // Return r_debug struct address 35 | return reinterpret_cast(rr_debug); 36 | } 37 | 38 | Breaker::Breaker(std::string binary_name, pid_t p) 39 | { 40 | pid = p; 41 | r_deb = get_r_debug(pid); 42 | name = binary_name; 43 | if (!r_deb) 44 | { 45 | fprintf(OUT, "%sERROR:%s Recovering r_debug struct failed\n", 46 | RED, NONE); 47 | throw std::logic_error("r_debug not found"); 48 | } 49 | } 50 | 51 | void Breaker::remove_breakpoint(std::string region, void* addr) 52 | { 53 | auto it = handled_syscalls.find(region); 54 | 55 | if (it == handled_syscalls.end()) 56 | { 57 | fprintf(OUT, 58 | "%sERROR:%s Region %s not found in map (remove)\n", 59 | RED, NONE, region.c_str()); 60 | return; 61 | } 62 | 63 | auto breaks = it->second; 64 | 65 | // No breakpoint found at this address 66 | if (breaks.find(addr) == breaks.end()) 67 | return; 68 | 69 | // Get saved instruction and rewrite it in memory 70 | ptrace(PTRACE_POKEDATA, pid, addr, breaks.find(addr)->second); 71 | handled_syscalls[region].erase(addr); 72 | } 73 | 74 | void Breaker::add_breakpoint(std::string r, void* addr) 75 | { 76 | // Get origin instruction and save it 77 | unsigned long instr = ptrace(PTRACE_PEEKDATA, pid, addr, 0); 78 | 79 | // Address already patched 80 | // r stands for region 81 | if (handled_syscalls[r].find(addr) != handled_syscalls[r].end()) 82 | handled_syscalls[r].find(addr)->second = instr; 83 | else 84 | handled_syscalls[r][addr] = instr; 85 | 86 | // Replace it with an int3 (CC) opcode sequence 87 | ptrace(PTRACE_POKEDATA, pid, addr, (instr & TRAP_MASK) | TRAP_INST); 88 | } 89 | 90 | char Breaker::is_from_us(void* addr) const 91 | { 92 | for (auto& it : handled_syscalls) 93 | if (it.second.find(addr) != it.second.end()) 94 | return 1; 95 | return 0; 96 | } 97 | 98 | long Breaker::handle_bp(void* addr, bool p, Tracker& t) 99 | { 100 | if (addr == rr_brk) 101 | { 102 | int state = 0; 103 | void* link_map = get_link_map(r_deb, pid, &state); 104 | 105 | if (state == r_debug::RT_CONSISTENT) 106 | browse_link_map(link_map, pid, this); 107 | 108 | return NO_SYSCALL; 109 | } 110 | else 111 | for (auto it : handled_syscalls) 112 | if (it.second.find(addr) != it.second.end()) 113 | // p stands for 'print' 114 | return exec_breakpoint(it.first, addr, p, t); 115 | 116 | return SYSCALL_ERROR; 117 | } 118 | 119 | long Breaker::handle_bp(void* addr, bool print) 120 | { 121 | if (addr == rr_brk) 122 | { 123 | int state = 0; 124 | void* link_map = get_link_map(r_deb, pid, &state); 125 | 126 | if (state == r_debug::RT_CONSISTENT) 127 | browse_link_map(link_map, pid, this); 128 | 129 | return NO_SYSCALL; 130 | } 131 | else 132 | for (auto it : handled_syscalls) 133 | if (it.second.find(addr) != it.second.end()) 134 | return exec_breakpoint(it.first, addr, print); 135 | 136 | return SYSCALL_ERROR; 137 | 138 | } 139 | 140 | long Breaker::exec_breakpoint(std::string r, void* addr, bool p, Tracker& t) 141 | { 142 | int wait_status = 0; 143 | 144 | // Not found 145 | auto it = handled_syscalls.find(r); 146 | if (it->second.find(addr) == it->second.end()) 147 | return NO_SYSCALL; 148 | 149 | struct user_regs_struct regs; 150 | if ((it->second.find(addr)->second & 0xFF) == TRAP_INST) 151 | { 152 | sanity_customs(pid, t, 0); 153 | ptrace(PTRACE_SINGLESTEP, pid, 0, 0); 154 | waitpid(pid, 0, 0); 155 | 156 | return CUSTOM_BREAKPOINT; 157 | } 158 | 159 | // Restore old instruction pointer 160 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 161 | regs.XIP -= 1; 162 | ptrace(PTRACE_SETREGS, pid, 0, ®s); 163 | 164 | p ? print_syscall(pid, regs.XAX) : p = p; 165 | 166 | // Run instruction 167 | remove_breakpoint(r, addr); 168 | 169 | ptrace(PTRACE_SINGLESTEP, pid, 0, 0); 170 | 171 | waitpid(pid, &wait_status, 0); 172 | sanity_customs(pid, t, 0); 173 | 174 | p ? print_retval(pid, regs.XAX) : p = p; 175 | 176 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 177 | long retval = regs.XAX; 178 | if (WIFEXITED(wait_status)) 179 | throw std::logic_error("EXITED"); 180 | add_breakpoint(r, addr); 181 | 182 | return retval; 183 | 184 | } 185 | long Breaker::exec_breakpoint(std::string region, void* addr, bool print) 186 | { 187 | int wait_status = 0; 188 | 189 | // Not found 190 | auto it = handled_syscalls.find(region); 191 | if (it->second.find(addr) == it->second.end()) 192 | return NO_SYSCALL; 193 | 194 | struct user_regs_struct regs; 195 | 196 | if ((it->second.find(addr)->second & 0xFF) == TRAP_INST) 197 | { 198 | ptrace(PTRACE_SINGLESTEP, pid, 0, 0); 199 | waitpid(pid, 0, 0); 200 | return CUSTOM_BREAKPOINT; 201 | } 202 | 203 | // Restore old instruction pointer 204 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 205 | regs.XIP -= 1; 206 | ptrace(PTRACE_SETREGS, pid, 0, ®s); 207 | 208 | print ? print_syscall(pid, regs.XAX) : print = print; 209 | 210 | // Run instruction 211 | remove_breakpoint(region, addr); 212 | ptrace(PTRACE_SINGLESTEP, pid, 0, 0); 213 | 214 | waitpid(pid, &wait_status, 0); 215 | 216 | print ? print_retval(pid, regs.XAX) : print = print; 217 | 218 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 219 | long retval = regs.XAX; 220 | if (WIFEXITED(wait_status)) 221 | throw std::logic_error("EXITED"); 222 | add_breakpoint(region, addr); 223 | 224 | return retval; 225 | } 226 | 227 | void Breaker::print_bps() const 228 | { 229 | /* 230 | ** For debugging purposes 231 | */ 232 | printf("Number of zones: %ld\n{\n", handled_syscalls.size()); 233 | for (auto& region : handled_syscalls) 234 | { 235 | printf("\tNumber of breakpoints in %s: %s%ld%s\n\t{\n", 236 | region.first.c_str(), BLUE, region.second.size(), NONE); 237 | int i = 0; 238 | for (auto& iter : region.second) 239 | { 240 | unsigned long instr = 241 | ptrace(PTRACE_PEEKDATA, pid, iter.first, 0); 242 | if (iter.first == rr_brk) 243 | fprintf(OUT, "\t\t%3d: %p (r_brk):\n", i, 244 | iter.first); 245 | else 246 | fprintf(OUT, "\t\t%3d: %p :\n", i, iter.first); 247 | 248 | fprintf(OUT, "\t\t\t%8lx (origin)\n", iter.second); 249 | fprintf(OUT, "\t\t\t%8lx (actual)\n", instr); 250 | i++; 251 | } 252 | printf("\t}\n"); 253 | } 254 | printf("}\n"); 255 | printf("Exiting\n"); 256 | exit(0); // For debug purposes 257 | } 258 | 259 | void Breaker::reset_libs(void* link_map) 260 | { 261 | /* 262 | ** Crappy version 263 | ** 264 | ** Correct version would be to iterate through the libs 265 | ** list and check the one that is NOT into the map 266 | ** But fuck it, already short on time 267 | **/ 268 | 269 | for (auto& region : handled_syscalls) 270 | { 271 | if (!strcmp(region.first.c_str(), MAIN_CHILD)) 272 | continue; 273 | handled_syscalls.erase(region.first); 274 | } 275 | browse_link_map(link_map, pid, this); 276 | } 277 | -------------------------------------------------------------------------------- /src/level2/dig_into_mem.cc: -------------------------------------------------------------------------------- 1 | #include "level2.hh" 2 | 3 | /* 4 | ** Very large chunk of functions used to 5 | ** dig into (mostly the child's) memory and 6 | ** access interesting stuff 7 | ** 8 | ** Needs some serious optimisations and refactoring 9 | ** 10 | ** TODO : Create a function to refactor at least the 11 | ** process_vm_* 12 | */ 13 | 14 | void* get_r_brk(void* rr_debug, pid_t pid_child) 15 | { 16 | struct iovec local; 17 | struct iovec remote; 18 | char buffer[128] = { 0 }; 19 | local.iov_base = buffer; 20 | local.iov_len = sizeof (struct r_debug);; 21 | remote.iov_base = rr_debug; 22 | remote.iov_len = sizeof (struct r_debug); 23 | 24 | process_vm_readv(pid_child, &local, 1, &remote, 1, 0); 25 | 26 | auto tmp = reinterpret_cast(buffer); 27 | 28 | return (void*)tmp->r_brk; 29 | } 30 | 31 | void* get_final_r_debug(Elf64_Dyn* dt_struct, pid_t pid_child) 32 | { 33 | Elf64_Dyn child_dyn; 34 | struct iovec local; 35 | struct iovec remote; 36 | 37 | // Loop until DT_DEBUG 38 | local.iov_base = &child_dyn; 39 | local.iov_len = sizeof (Elf64_Dyn); 40 | remote.iov_base = dt_struct; 41 | remote.iov_len = sizeof (Elf64_Dyn); 42 | 43 | while (true) 44 | { 45 | for (Elf64_Dyn* cur = dt_struct; ; ++cur) 46 | { 47 | remote.iov_base = cur; 48 | process_vm_readv(pid_child, &local, 1, &remote, 1, 0); 49 | if (child_dyn.d_tag == DT_DEBUG) 50 | break; 51 | } 52 | if (child_dyn.d_un.d_ptr) 53 | break; 54 | 55 | ptrace(PTRACE_SINGLESTEP, pid_child, NULL, NULL); 56 | waitpid(pid_child, 0, 0); 57 | } 58 | 59 | return reinterpret_cast(child_dyn.d_un.d_ptr); 60 | 61 | } 62 | 63 | void* get_pt_dynamic(unsigned long phent, unsigned long phnum, 64 | pid_t pid_child, void* at_phdr) 65 | { 66 | // Loop on the Program header until the PT_DYNAMIC entry 67 | Elf64_Dyn* dt_struct = NULL; 68 | struct iovec local; 69 | struct iovec remote; 70 | char buffer[128]; 71 | Elf64_Phdr* phdr; 72 | for (unsigned i = 0; i < phnum; ++i) 73 | { 74 | local.iov_base = buffer; 75 | local.iov_len = sizeof (Elf64_Phdr); 76 | remote.iov_base = (char*)at_phdr + i * phent; 77 | remote.iov_len = sizeof (Elf64_Phdr); 78 | 79 | process_vm_readv(pid_child, &local, 1, &remote, 1, 0); 80 | 81 | phdr = reinterpret_cast(buffer); 82 | 83 | if (phdr->p_type == PT_DYNAMIC) 84 | { 85 | // First DT_XXXX entry 86 | dt_struct = 87 | reinterpret_cast(phdr->p_vaddr); 88 | break; 89 | } 90 | } 91 | 92 | if (!dt_struct) 93 | throw std::logic_error("PT_DYNAMIC not found"); 94 | 95 | return (void*) dt_struct; 96 | } 97 | 98 | 99 | void* get_phdr(unsigned long& phent, unsigned long& phnum, pid_t pid_child) 100 | { 101 | // Open proc/[pid]/auxv 102 | 103 | std::ostringstream ss; 104 | ss << "/proc/" << pid_child << "/auxv"; 105 | auto file = ss.str(); 106 | int fd = open(file.c_str(), std::ios::binary); 107 | 108 | ElfW(auxv_t) auxv_; 109 | 110 | void* at_phdr = NULL; 111 | 112 | // Read from flux until getting all the interesting data 113 | while (read(fd, &auxv_, sizeof (auxv_)) > -1) 114 | { 115 | if (auxv_.a_type == AT_PHDR) 116 | at_phdr = (void*)auxv_.a_un.a_val; 117 | 118 | if (auxv_.a_type == AT_PHENT) 119 | phent = auxv_.a_un.a_val; 120 | 121 | if (auxv_.a_type == AT_PHNUM) 122 | phnum = auxv_.a_un.a_val; 123 | 124 | if (phnum && phent && at_phdr) 125 | break; 126 | } 127 | close(fd); 128 | 129 | return at_phdr; 130 | } 131 | 132 | void* get_link_map(void* rr_debug, pid_t pid, int* status) 133 | { 134 | char buffer[128]; 135 | struct iovec local; 136 | struct iovec remote; 137 | local.iov_base = buffer; 138 | local.iov_len = sizeof (struct r_debug); 139 | remote.iov_base = rr_debug; 140 | remote.iov_len = sizeof (Elf64_Phdr); 141 | 142 | process_vm_readv(pid, &local, 1, &remote, 1, 0); 143 | 144 | auto tmp = (struct r_debug*)buffer; 145 | 146 | struct link_map* link_map = tmp->r_map; 147 | 148 | tmp = (struct r_debug*)buffer; 149 | *status = tmp->r_state; 150 | return link_map; 151 | } 152 | 153 | void* print_string_from_mem(void* str, pid_t pid) 154 | { 155 | char s[64] = {0}; 156 | struct iovec local; 157 | struct iovec remote; 158 | local.iov_base = &s; 159 | local.iov_len = sizeof (struct link_map); 160 | remote.iov_base = str; 161 | remote.iov_len = sizeof (struct link_map); 162 | 163 | ssize_t read = process_vm_readv(pid, &local, 1, &remote, 1, 0); 164 | 165 | if (read) 166 | { 167 | // Used to print the string 168 | // Now returns it 169 | //fprintf(OUT, "%s\n", s); 170 | return strdup(s); 171 | } 172 | return NULL; 173 | } 174 | 175 | std::pairget_sections(const char* lib_name, Breaker& b) 176 | { 177 | int fd = open(lib_name, O_RDONLY); 178 | if (fd < 0) 179 | return std::pair(0, 0); 180 | 181 | ElfW(Ehdr) elf_header; 182 | ElfW(Shdr) section_header; 183 | ElfW(Shdr) string_header; 184 | bool in_executable = false; 185 | off_t offset = 0; 186 | long len = 0; 187 | 188 | // Elf header 189 | unsigned nread = read(fd, &elf_header, sizeof (ElfW(Ehdr))); 190 | b.program_entry_point = (void*)elf_header.e_entry; 191 | 192 | // String table offset 193 | lseek(fd, elf_header.e_shoff, SEEK_CUR); 194 | int string_table_offset = elf_header.e_shstrndx; 195 | lseek(fd, elf_header.e_shentsize 196 | * (string_table_offset - 1), SEEK_CUR); 197 | 198 | // String table 199 | nread = read(fd, &string_header, sizeof (ElfW(Shdr))); 200 | off_t strtab = string_header.sh_offset; 201 | lseek(fd, strtab, SEEK_SET); 202 | char* table = new char[MAX_STRING_SIZE * elf_header.e_shnum]; 203 | nread = read(fd, table, sizeof (char) 204 | * MAX_STRING_SIZE * elf_header.e_shnum); 205 | 206 | // Section headers 207 | int i; 208 | for (i = 0; i < elf_header.e_shnum; ++i) 209 | { 210 | char buff[255] = { 0 }; 211 | lseek(fd, 212 | elf_header.e_shoff 213 | + elf_header.e_shentsize * i, SEEK_SET); 214 | 215 | nread = read(fd, §ion_header, sizeof (ElfW(Shdr))); 216 | 217 | if (in_executable 218 | && !(section_header.sh_flags & SHF_EXECINSTR)) 219 | break; 220 | 221 | if (!in_executable && section_header.sh_flags & SHF_EXECINSTR) 222 | { 223 | offset = section_header.sh_offset; 224 | in_executable = true; 225 | } 226 | 227 | if (in_executable) 228 | { 229 | len += section_header.sh_size; 230 | for (int j = section_header.sh_name; 231 | table[j] != '\0'; ++j) 232 | buff[j - section_header.sh_name] = table[j]; 233 | UNUSED(buff[0]); 234 | } 235 | 236 | lseek(fd, elf_header.e_shentsize, SEEK_CUR); 237 | } 238 | 239 | UNUSED(nread); 240 | delete[] table; 241 | return std::pair(offset, len); 242 | } 243 | 244 | int disass(const char* name, void* offset, long len, Breaker& b, pid_t pid) 245 | { 246 | errno = 0; 247 | csh handle; 248 | cs_insn* insn = NULL; 249 | size_t count = 0; 250 | struct iovec local; 251 | struct iovec remote; 252 | 253 | int counter = 0; 254 | int syscall_counter = 0; 255 | for (unsigned i = 0; i < len / PAGE_SIZE + 1; ++i) 256 | { 257 | unsigned char buffer[PAGE_SIZE]; 258 | local.iov_base = &buffer; 259 | local.iov_len = PAGE_SIZE - 1; 260 | remote.iov_base = offset + i * PAGE_SIZE; 261 | remote.iov_len = PAGE_SIZE - 1; 262 | int nread = process_vm_readv(pid, &local, 1, &remote, 1, 0); 263 | 264 | if (nread < 0) 265 | return -1; 266 | 267 | if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) 268 | return -1; 269 | 270 | cs_option(handle, CS_OPT_SKIPDATA, CS_OPT_ON); 271 | count = cs_disasm(handle, buffer, nread, 272 | (uintptr_t)offset + 273 | PAGE_SIZE* 274 | i, 0, &insn); 275 | 276 | if (count > 0) 277 | { 278 | for (size_t j = 0; j < count && counter <= len; j++) 279 | { 280 | memset(insn, 0, sizeof (cs_insn)); 281 | auto id = insn[j].id; 282 | 283 | // If syscall, add breakpoint 284 | if (id == X86_INS_SYSENTER 285 | || id == X86_INS_SYSCALL 286 | || (id == X86_INS_INT 287 | && insn[j].bytes[1] == 0x80) 288 | || id == X86_INS_INT3) 289 | { 290 | syscall_counter++; 291 | b.add_breakpoint(std::string(name), 292 | (void*)insn[j].address); 293 | } 294 | counter += insn[j].size; 295 | 296 | } 297 | 298 | cs_free(insn, count); 299 | } 300 | else 301 | printf("ERROR: Failed to disassemble given code!\n"); 302 | cs_close(& handle); 303 | } 304 | return syscall_counter; 305 | } 306 | 307 | void browse_link_map(void* link_m, pid_t pid, Breaker* b) 308 | { 309 | struct link_map map; 310 | struct iovec local; 311 | struct iovec remote; 312 | local.iov_base = ↦ 313 | local.iov_len = sizeof (struct link_map); 314 | remote.iov_base = link_m; 315 | remote.iov_len = sizeof (struct link_map); 316 | 317 | process_vm_readv(pid, &local, 1, &remote, 1, 0); 318 | 319 | while (map.l_prev) 320 | { 321 | remote.iov_base = map.l_prev; 322 | process_vm_readv(pid, &local, 1, &remote, 1, 0); 323 | } 324 | 325 | do 326 | { 327 | process_vm_readv(pid, &local, 1, &remote, 1, 0); 328 | if (map.l_addr) 329 | { 330 | /* 331 | ** Unlike what the elf.h file can say about it 332 | ** l_addr is not a difference but the base address 333 | ** the shared object is loaded at. 334 | **/ 335 | char* dupp = (char*)print_string_from_mem(map.l_name, 336 | pid); 337 | 338 | std::pairsections = get_sections(dupp, *b); 339 | if (sections.second) 340 | disass(dupp, 341 | (char*)map.l_addr + sections.first, 342 | sections.second, * b, pid); 343 | 344 | free(dupp); 345 | } 346 | remote.iov_base = map.l_next; 347 | } while (map.l_next); 348 | 349 | // Add binary's own syscalls 350 | std::pair sections = get_sections(b->name.c_str(), * b); 351 | if (sections.second) 352 | disass(MAIN_CHILD, 353 | (char*)b->program_entry_point + sections.first, 354 | sections.second, * b, pid); 355 | } 356 | -------------------------------------------------------------------------------- /src/level2/mem_strace_hook.cc: -------------------------------------------------------------------------------- 1 | #include "level1.hh" 2 | #include "level2.hh" 3 | 4 | static int mem_hook(std::string name, pid_t pid) 5 | { 6 | setenv("LD_BIND_NOW", "1", 1); //FIXME : Potentialy bad 7 | int status = 0; 8 | waitpid(pid, &status, 0); 9 | 10 | Breaker b(name, pid); 11 | b.add_breakpoint(MAIN_CHILD, b.rr_brk); 12 | 13 | while (1) 14 | { 15 | ptrace(PTRACE_CONT, pid, 0, 0); 16 | 17 | waitpid(pid, &status, 0); 18 | 19 | long addr = ptrace(PTRACE_PEEKUSER, pid, 20 | sizeof (long) * INSTR_REG); 21 | auto bp = (void*)(addr - 1); 22 | 23 | 24 | if (WIFEXITED(status)) 25 | break; 26 | 27 | if (WIFSIGNALED(status)) 28 | break; 29 | 30 | // Segfault 31 | if (status == 2943) 32 | break; 33 | 34 | try 35 | { 36 | if (b.is_from_us(bp)) 37 | b.handle_bp(bp, true); 38 | 39 | } 40 | catch (std::logic_error) { break; } 41 | } 42 | 43 | 44 | ptrace(PTRACE_CONT, pid, 0, 0); 45 | return 0; 46 | } 47 | 48 | int main(int argc, char** argv) 49 | { 50 | if (argc < 2) 51 | { 52 | fprintf(OUT, "Usage: %s binary_to_trace[ARGS]\n", argv[0]); 53 | return 0; 54 | } 55 | 56 | std::string name = argv[1]; 57 | 58 | if (!binary_exists(name) && name.find("./") != std::string::npos) 59 | { 60 | fprintf(OUT, "%sERROR:%s Binary %s not found.\n", 61 | RED, NONE, name.c_str()); 62 | exit(-1); 63 | } 64 | 65 | pid_t pid = 0; 66 | 67 | if ((pid = fork()) != 0) 68 | return mem_hook(name, pid); 69 | 70 | return run_child(argc - 1, argv + 1, NULL); 71 | } 72 | -------------------------------------------------------------------------------- /src/level3/mem_tracker.cc: -------------------------------------------------------------------------------- 1 | #include "level1.hh" 2 | #include "level2.hh" 3 | #define LEVEL 3 4 | 5 | static int mem_tracker(std::string name, pid_t pid) 6 | { 7 | setenv("LD_BIND_NOW", "1", 1); //FIXME : Potentialy bad 8 | int status = 0; 9 | waitpid(pid, &status, 0); 10 | 11 | Breaker b(name, pid); 12 | Tracker t(name, pid); 13 | 14 | b.add_breakpoint(MAIN_CHILD, b.rr_brk); 15 | 16 | while (1) 17 | { 18 | ptrace(PTRACE_CONT, pid, 0, 0); 19 | 20 | waitpid(pid, &status, 0); 21 | 22 | struct user_regs_struct regs; 23 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 24 | long long addr = regs.XIP; 25 | 26 | auto bp = (void*)(addr - 1); 27 | 28 | if (WIFEXITED(status)) 29 | break; 30 | 31 | if (WIFSIGNALED(status)) 32 | break; 33 | 34 | // Segfault 35 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) 36 | { 37 | fprintf(OUT, "[%d] Signal 11 caught (SIGSEGV)\n", pid); 38 | break; 39 | } 40 | try 41 | { 42 | if (!addr) 43 | break; 44 | 45 | if (!b.is_from_us(bp)) 46 | continue; 47 | 48 | long syscall = NO_SYSCALL; 49 | 50 | if (bp != b.rr_brk) 51 | syscall = get_xax(pid); 52 | 53 | t.handle_syscall(syscall, b, bp, true); 54 | 55 | } 56 | catch (std::logic_error) { break; } 57 | } 58 | 59 | ptrace(PTRACE_CONT, pid, 0, 0); 60 | return 0; 61 | } 62 | 63 | 64 | 65 | int main(int argc, char** argv) 66 | { 67 | 68 | if (argc < 2) 69 | { 70 | fprintf(OUT, 71 | "Usage: %s[--preload lib] binary_to_trace[ARGS]\n", 72 | argv[0]); 73 | return 0; 74 | } 75 | 76 | std::string name = argv[1]; 77 | 78 | char* preload = get_cmd_opt(argv, argv + argc, "--preload"); 79 | if (preload) 80 | name = argv[3]; 81 | else 82 | { 83 | if (!binary_exists(name) 84 | && name.find("--") != std::string::npos) 85 | { 86 | fprintf(OUT, 87 | "%sERROR:%s Invalid command option (%s)\n", 88 | RED, NONE, name.c_str()); 89 | exit(-1); 90 | } 91 | } 92 | 93 | if (!binary_exists(name) && name.find("./") != std::string::npos) 94 | { 95 | fprintf(OUT, "%sERROR:%s Binary %s not found.\n", 96 | RED, NONE, name.c_str()); 97 | exit(-1); 98 | } 99 | 100 | 101 | pid_t pid = 0; 102 | 103 | if ((pid = fork()) != 0) 104 | return mem_tracker(name, pid); 105 | 106 | return run_child(argc - 1, argv + 1, preload); 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/level3/memory_hooks.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include "shared.hh" 6 | // http://elinux.org/images/b/b5/Elc2013_Kobayashi.pdf 7 | 8 | void* malloc(size_t size) 9 | { 10 | static void* (*my_malloc)(size_t) = NULL; 11 | 12 | if (!my_malloc) 13 | my_malloc = dlsym(RTLD_NEXT, "malloc"); 14 | 15 | void *p = my_malloc(size); 16 | 17 | asm volatile ("int3" 18 | : 19 | : "a" (CUSTOM_SYSCALL_MALLOC), 20 | "b" (p), 21 | "c" (size)); 22 | 23 | return p; 24 | } 25 | 26 | void* calloc(size_t nmemb, size_t size) 27 | { 28 | static void* (*my_calloc)(size_t, size_t) = NULL; 29 | 30 | if (!my_calloc) 31 | my_calloc = dlsym(RTLD_NEXT, "calloc"); 32 | 33 | void *p = my_calloc(nmemb, size); 34 | 35 | asm volatile ("int3" 36 | : 37 | : "a" (CUSTOM_SYSCALL_CALLOC), 38 | "b" (p), 39 | "c" (size * nmemb)); 40 | return p; 41 | } 42 | 43 | void* realloc(void* ptr, size_t size) 44 | { 45 | static void* (*my_realloc)(void*, size_t) = NULL; 46 | 47 | if (!my_realloc) 48 | my_realloc = dlsym(RTLD_NEXT, "realloc"); 49 | 50 | void *p = my_realloc(ptr, size); 51 | 52 | asm volatile ("int3" 53 | : 54 | : "a" (CUSTOM_SYSCALL_REALLOC), 55 | "b" (p), 56 | "c" (size), 57 | "d" (ptr)); 58 | return p; 59 | } 60 | 61 | void free(void* ptr) 62 | { 63 | static void* (*my_free)(void*) = NULL; 64 | 65 | if (!my_free) 66 | my_free = dlsym(RTLD_NEXT, "free"); 67 | 68 | my_free(ptr); 69 | 70 | asm volatile ("int3" 71 | : 72 | : "a" (CUSTOM_SYSCALL_FREE), 73 | "b" (ptr)); 74 | } 75 | -------------------------------------------------------------------------------- /src/level3/tracker.cc: -------------------------------------------------------------------------------- 1 | #include "level4.hh" 2 | 3 | static bool compare_address(Mapped first, Mapped second) 4 | { 5 | // Prettier than a direct return, sorry 6 | char* first_addr = (char*)first.mapped_begin; 7 | char* second_addr = (char*)second.mapped_begin; 8 | return first_addr < second_addr; 9 | } 10 | 11 | bool Mapped::area_contains(unsigned long addr) const 12 | { 13 | int ret = (addr < mapped_begin + mapped_length) 14 | && addr >= mapped_begin; 15 | return ret; 16 | } 17 | 18 | 19 | bool Tracker::of_interest(int syscall) const 20 | { 21 | return syscall == MMAP_SYSCALL 22 | || syscall == MREMAP_SYSCALL 23 | || syscall == MUNMAP_SYSCALL 24 | || syscall == MPROTECT_SYSCALL 25 | || syscall == BRK_SYSCALL 26 | || syscall == CUSTOM_SYSCALL_MALLOC 27 | || syscall == CUSTOM_SYSCALL_CALLOC 28 | || syscall == CUSTOM_SYSCALL_REALLOC 29 | || syscall == CUSTOM_SYSCALL_FREE; 30 | } 31 | 32 | std::list::iterator Tracker::get_mapped(unsigned long addr) 33 | { 34 | for (auto it = mapped_areas.begin(); it != mapped_areas.end(); it++) 35 | if (it->area_contains(addr)) 36 | return it; 37 | return mapped_areas.end(); 38 | } 39 | 40 | 41 | int Tracker::handle_mprotect(Breaker& b, void* bp, bool print) 42 | { 43 | struct user_regs_struct regs; 44 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 45 | 46 | #if LEVEL == 4 47 | auto retval = b.handle_bp(bp, false, *this); 48 | #else 49 | auto retval = b.handle_bp(bp, false); 50 | #endif 51 | 52 | 53 | if (retval < 0) 54 | return retval; 55 | 56 | auto it = get_mapped(regs.rdi); 57 | if (it == mapped_areas.end()) 58 | return NOT_FOUND; 59 | 60 | if (print) 61 | lvl3_print_mprotect(0, regs.rdi, 62 | regs.rsi, it->mapped_protections); 63 | 64 | long tmp = reinterpret_cast(bp) - it->mapped_begin; 65 | regs.rsi -= tmp; 66 | 67 | if (regs.rsi > 0) 68 | regs.rsi = 0; 69 | 70 | it->mapped_protections = regs.rdx; 71 | for (unsigned i = 0; i < regs.rsi / PAGE_SIZE; ++i) 72 | { 73 | it = std::next(it); 74 | it->mapped_protections = regs.rdx; 75 | } 76 | 77 | if (print) 78 | lvl3_print_mprotect(1, regs.rdi, 79 | regs.rsi, it->mapped_protections); 80 | 81 | return retval; 82 | } 83 | 84 | 85 | void Tracker::tail_remove(std::list::iterator it, int iteration) 86 | { 87 | if (iteration > 0 && (std::next(it) != mapped_areas.end())) 88 | tail_remove(std::next(it), iteration - 1); 89 | 90 | mapped_areas.erase(it); 91 | return; 92 | } 93 | 94 | 95 | int Tracker::handle_mremap(Breaker& b, void* bp, bool print) 96 | { 97 | struct user_regs_struct regs; 98 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 99 | 100 | #if LEVEL == 4 101 | auto retval = b.handle_bp(bp, false, *this); 102 | #else 103 | auto retval = b.handle_bp(bp, false); 104 | #endif 105 | 106 | if ((void*) retval == MAP_FAILED) 107 | return retval; 108 | 109 | auto it = get_mapped(regs.rdi); 110 | if (it == mapped_areas.end()) 111 | return NOT_FOUND; 112 | 113 | if (print) 114 | lvl3_print_mremap(0, regs.rdi, 115 | regs.rsi, it->mapped_protections); 116 | 117 | if ((unsigned long)retval != it->mapped_begin) 118 | { 119 | it->mapped_begin = retval; 120 | it->mapped_length = regs.rdx; 121 | tail_remove(it, regs.rsi / regs.rdx); 122 | } 123 | 124 | // Old size == New size 125 | else if (regs.rsi == regs.rdx) 126 | return retval; 127 | 128 | // Old size > New size <==> Shrinking 129 | else if (regs.rsi > regs.rdx) 130 | { 131 | it->mapped_length = regs.rdx; 132 | auto tmp = regs.rsi / regs.rdx; 133 | tail_remove(std::next(it), tmp - 1); 134 | } 135 | // Expand 136 | else 137 | { 138 | unsigned i; 139 | for (i = 0; i < regs.rdx / PAGE_SIZE; ++i) 140 | { 141 | long addr = retval + i * PAGE_SIZE; 142 | mapped_areas.push_back(Mapped(addr, PAGE_SIZE, 143 | it->mapped_protections, 144 | id_inc++)); 145 | } 146 | 147 | if (regs.rdx % PAGE_SIZE) 148 | { 149 | long addr = retval + i * PAGE_SIZE; 150 | long len = regs.rdx % PAGE_SIZE; 151 | mapped_areas.push_back(Mapped(addr, len, 152 | it->mapped_protections, 153 | id_inc++)); 154 | } 155 | mapped_areas.erase(it); 156 | 157 | } 158 | if (print) 159 | lvl3_print_mremap(1, retval, 160 | regs.rdx, it->mapped_protections); 161 | mapped_areas.sort(compare_address); 162 | return retval; 163 | } 164 | 165 | int Tracker::handle_mmap(Breaker& b, void* bp, bool print) 166 | { 167 | struct user_regs_struct regs; 168 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 169 | 170 | #if LEVEL == 4 171 | auto retval = b.handle_bp(bp, false, *this); 172 | #else 173 | auto retval = b.handle_bp(bp, false); 174 | #endif 175 | if ((void*) retval == MAP_FAILED) 176 | return retval; 177 | 178 | if ((regs.r10 & MAP_SHARED) || !(regs.r10 & MAP_ANONYMOUS)) 179 | return retval; 180 | 181 | unsigned i = 0; 182 | for (i = 0; i < regs.rsi / PAGE_SIZE; ++i) 183 | { 184 | long addr = retval + i * PAGE_SIZE; 185 | mapped_areas.push_back(Mapped(addr, PAGE_SIZE, 186 | regs.rdx, id_inc++)); 187 | } 188 | 189 | if (regs.rsi % PAGE_SIZE) 190 | { 191 | long addr = retval + i * PAGE_SIZE; 192 | long len = regs.rsi % PAGE_SIZE; 193 | mapped_areas.push_back(Mapped(addr, len, 194 | regs.rdx, id_inc++)); 195 | } 196 | if (print) 197 | fprintf(OUT, 198 | "mmap { addr = 0x%lx, len = 0x%llx, prot = %lld } \n", 199 | retval, regs.rsi, regs.rdx); 200 | 201 | mapped_areas.sort(compare_address); 202 | 203 | #if LEVEL == 4 204 | set_page_protection(retval, regs.rsi, PROT_EXEC & regs.rdx, pid); 205 | #endif 206 | return retval; 207 | } 208 | 209 | int Tracker::handle_brk(Breaker& b, void* bp, bool print) 210 | { 211 | static int origin_set = 0; 212 | struct user_regs_struct regs; 213 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 214 | 215 | if (print) 216 | lvl3_print_brk(0, origin_program_break, actual_program_break); 217 | 218 | 219 | 220 | #if LEVEL == 4 221 | long retval = b.handle_bp(bp, false, *this); 222 | #else 223 | long retval = b.handle_bp(bp, false); 224 | #endif 225 | 226 | if (retval < 0) 227 | return 0; 228 | 229 | if (!origin_set) 230 | { 231 | origin_set = 1; 232 | origin_program_break = (void*)retval; 233 | actual_program_break = (void*)retval; 234 | } 235 | else 236 | actual_program_break = (void*)retval; 237 | 238 | if (print) 239 | lvl3_print_brk(1, origin_program_break, actual_program_break); 240 | 241 | return 0; 242 | 243 | } 244 | 245 | int Tracker::handle_munmap(Breaker& b, void* bp, bool print) 246 | { 247 | struct user_regs_struct regs; 248 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 249 | 250 | #if LEVEL == 4 251 | long retval = b.handle_bp(bp, false, *this); 252 | #else 253 | long retval = b.handle_bp(bp, false); 254 | #endif 255 | 256 | if (retval < 0) 257 | return retval; 258 | 259 | auto it = get_mapped(regs.rdi); 260 | if (it == mapped_areas.end()) 261 | return NOT_FOUND; 262 | 263 | unsigned long long addr = regs.rdi; 264 | unsigned long long len = regs.rsi; 265 | 266 | if (len < it->mapped_length) 267 | { 268 | mapped_areas.push_back(Mapped(addr + len, 269 | it->mapped_length - len, 270 | it->mapped_protections, id_inc++)); 271 | } 272 | 273 | tail_remove(it, len / PAGE_SIZE); 274 | if (print) 275 | fprintf(OUT, "munmap { addr = 0x%llx, len = 0x%llx } \n", 276 | addr, len); 277 | mapped_areas.sort(compare_address); 278 | return retval; 279 | } 280 | 281 | int Tracker::custom_alloc(int prefix, Breaker& b, void* bp, bool print) 282 | { 283 | struct user_regs_struct regs; 284 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 285 | 286 | #if LEVEL == 4 287 | auto retval = b.handle_bp(bp, false, *this); 288 | #else 289 | auto retval = b.handle_bp(bp, false); 290 | #endif 291 | 292 | auto rbx = regs.rbx; 293 | auto rcx = regs.rcx; 294 | 295 | if (retval != CUSTOM_BREAKPOINT) 296 | return retval; 297 | 298 | 299 | mapped_areas.push_back(Mapped(rbx, rcx, MALLOC_CHILD, id_inc++)); 300 | 301 | if (print) 302 | { 303 | if (!prefix) 304 | fprintf(OUT, "malloc { addr = 0x%llx, len = 0x%llx } \n", 305 | rbx, rcx); 306 | else 307 | fprintf(OUT, "calloc { addr = 0x%llx, len = 0x%llx } \n", 308 | rbx, rcx); 309 | } 310 | 311 | #if LEVEL == 4 312 | set_page_protection(retval, regs.rsi, PROT_EXEC & regs.rdx, pid); 313 | #endif 314 | 315 | 316 | mapped_areas.sort(compare_address); 317 | nb_of_allocs++; 318 | 319 | return retval; 320 | 321 | } 322 | 323 | int Tracker::custom_free(Breaker& b, void* bp, bool print) 324 | { 325 | struct user_regs_struct regs; 326 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 327 | #if LEVEL == 4 328 | b.handle_bp(bp, false, *this); 329 | #else 330 | b.handle_bp(bp, false); 331 | #endif 332 | 333 | auto rbx = regs.rbx; 334 | auto it = get_mapped(rbx); 335 | 336 | if (it == mapped_areas.end()) 337 | return -1; 338 | 339 | if (it == mapped_areas.end() || it->mapped_protections != MALLOC_CHILD) 340 | { 341 | invalid_free(pid, (void*)rbx, *this); 342 | return 0; 343 | } 344 | 345 | if (print) 346 | fprintf(OUT, "free { addr = 0x%llx, len = 0x%lx } \n", 347 | rbx, it->mapped_length); 348 | 349 | mapped_areas.erase(it); 350 | 351 | nb_of_frees++; 352 | return 0; 353 | } 354 | 355 | 356 | int Tracker::custom_realloc(Breaker& b, void* bp, bool print) 357 | { 358 | struct user_regs_struct regs; 359 | ptrace(PTRACE_GETREGS, pid, NULL, ®s); 360 | 361 | 362 | #if LEVEL == 4 363 | b.handle_bp(bp, false, *this); 364 | #else 365 | b.handle_bp(bp, false); 366 | #endif 367 | 368 | auto rbx = regs.rbx; 369 | auto rcx = regs.rcx; 370 | auto rdx = regs.rdx; 371 | 372 | auto it = get_mapped(rdx); 373 | 374 | if (it == mapped_areas.end()) 375 | return -1; 376 | 377 | if (print) 378 | { 379 | lvl3_print_realloc(0, rdx, rbx, it->mapped_length); 380 | lvl3_print_realloc(1, rdx, rbx, rcx); 381 | } 382 | if (rbx != rcx) 383 | { 384 | mapped_areas.erase(it); 385 | mapped_areas.push_back(Mapped(rbx, rcx, MALLOC_CHILD, id_inc++)); 386 | } 387 | else 388 | it->mapped_length = rcx; 389 | 390 | nb_of_allocs++; 391 | 392 | mapped_areas.sort(compare_address); 393 | return 0; 394 | } 395 | 396 | 397 | int Tracker::handle_syscall(int syscall, Breaker& b, void* bp, bool print) 398 | { 399 | switch (syscall) 400 | { 401 | case MMAP_SYSCALL: 402 | return handle_mmap(b, bp, print); 403 | case MUNMAP_SYSCALL: 404 | return handle_munmap(b, bp, print); 405 | case MPROTECT_SYSCALL: 406 | return handle_mprotect(b, bp, print); 407 | case MREMAP_SYSCALL: 408 | return handle_mremap(b, bp, print); 409 | case BRK_SYSCALL: 410 | return handle_brk(b, bp, print); 411 | case CUSTOM_SYSCALL_MALLOC: 412 | return custom_alloc(0, b, bp, print); 413 | case CUSTOM_SYSCALL_CALLOC: 414 | return custom_alloc(1, b, bp, print); 415 | case CUSTOM_SYSCALL_REALLOC: 416 | return custom_realloc(b, bp, print); 417 | case CUSTOM_SYSCALL_FREE: 418 | return custom_free(b, bp, print); 419 | } 420 | 421 | #if LEVEL == 4 422 | return b.handle_bp(bp, false, *this); 423 | #else 424 | return b.handle_bp(bp, false); 425 | #endif 426 | 427 | } 428 | 429 | void Tracker::print_mapped_areas() const 430 | { 431 | // Debug purposes 432 | 433 | printf("Origin process break %p\n", origin_program_break); 434 | printf("Actual process break %p\n", actual_program_break); 435 | for (auto it = mapped_areas.begin(); it != mapped_areas.end(); it++) 436 | { 437 | fprintf(OUT, "Mapped area #%d\n", it->id); 438 | fprintf(OUT, "\tBegins:\t%p\n", (void*)it->mapped_begin); 439 | fprintf(OUT, "\tLength:\t%ld\n", it->mapped_length); 440 | fprintf(OUT, "\tEnds :\t%p\n", (char*)it->mapped_begin 441 | + it->mapped_length); 442 | fprintf(OUT, "\tProt :\t%lx\n\n", it->mapped_protections); 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/level4/injector.cc: -------------------------------------------------------------------------------- 1 | #include "level4.hh" 2 | 3 | int remove_page_protection(pid_t pid, Tracker& t) 4 | { 5 | for (auto it = t.mapped_areas.begin(); it != t.mapped_areas.end(); it++) 6 | set_page_protection(it->mapped_begin, it->mapped_length, 7 | PROT_EXEC * it->executable_bit, pid); 8 | 9 | unsigned long begin_brk = reinterpret_cast(t.origin_program_break); 10 | unsigned long end_brk = reinterpret_cast(t.actual_program_break); 11 | 12 | set_page_protection(begin_brk, end_brk - begin_brk, PROT_NONE, pid); 13 | 14 | return 0; 15 | } 16 | 17 | int reset_page_protection(pid_t pid, Tracker& t) 18 | { 19 | for (auto it = t.mapped_areas.begin(); it != t.mapped_areas.end(); it++) 20 | set_page_protection(it->mapped_begin, it->mapped_length, 21 | it->mapped_protections, pid); 22 | 23 | unsigned long begin_brk = reinterpret_cast(t.origin_program_break); 24 | unsigned long end_brk = reinterpret_cast(t.actual_program_break); 25 | 26 | 27 | set_page_protection(begin_brk, end_brk - begin_brk, PROT_READ | PROT_WRITE, pid); 28 | return 0; 29 | } 30 | 31 | int set_page_protection(unsigned long addr, size_t len, unsigned long prot, pid_t pid) 32 | { 33 | struct user_regs_struct regs; 34 | struct user_regs_struct bckp; 35 | int status = 0; 36 | unsigned long overriden = 0; 37 | // Backup 38 | ptrace(PTRACE_GETREGS, pid, ®s, ®s); 39 | bckp = regs; 40 | 41 | // Set new registers for mprotect 42 | regs.rdi = addr - addr % PAGE_SIZE; 43 | regs.rsi = len + len % PAGE_SIZE; 44 | regs.rdx = prot; 45 | regs.rax = MPROTECT_SYSCALL; 46 | overriden = ptrace(PTRACE_PEEKDATA, pid, regs.rip, sizeof (unsigned long)); 47 | ptrace(PTRACE_POKEDATA, pid, regs.rip, SYSCALL); 48 | ptrace(PTRACE_SETREGS, pid, ®s, ®s); 49 | 50 | // Launch syscall 51 | ptrace(PTRACE_SINGLESTEP, pid, 0, 0); 52 | waitpid(pid, &status, 0); 53 | 54 | ptrace(PTRACE_SETREGS, pid, &bckp, &bckp); 55 | ptrace(PTRACE_POKEDATA, pid, bckp.rip, overriden); 56 | return 0; 57 | } 58 | 59 | int handle_injected_sigsegv(pid_t pid, Tracker& t) 60 | { 61 | // Malloc fix 62 | long tmp = get_xip(pid); 63 | if (MALLOC_STUFF_ADDRESS > tmp) 64 | sanity_customs(pid, t, 0); 65 | 66 | reset_page_protection(pid, t); 67 | 68 | int status = 0; 69 | ptrace(PTRACE_SINGLESTEP, pid, 0, 0); 70 | waitpid(pid, &status, 0); 71 | 72 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) 73 | sanity_customs(pid, t, SEGFAULT); 74 | 75 | remove_page_protection(pid, t); 76 | return 0; 77 | } 78 | 79 | int handle_injected_syscall(int syscall, Breaker& b, void* bp, Tracker& t) 80 | { 81 | bool print = false; 82 | reset_page_protection(b.pid, t); 83 | t.handle_syscall(syscall, b, bp, print); 84 | remove_page_protection(b.pid, t); 85 | return 0; 86 | } 87 | -------------------------------------------------------------------------------- /src/level4/mem_checker.cc: -------------------------------------------------------------------------------- 1 | #include "level1.hh" 2 | #include "level2.hh" 3 | #include "level4.hh" 4 | 5 | static int mem_checker(std::string name, pid_t pid) 6 | { 7 | setenv("LD_BIND_NOW", "1", 1); //FIXME : Potentialy bad 8 | int status = 0; 9 | waitpid(pid, &status, 0); 10 | 11 | Breaker b(name, pid); 12 | Tracker t(name, pid); 13 | 14 | b.add_breakpoint(MAIN_CHILD, b.rr_brk); 15 | 16 | 17 | fprintf(OUT, "[%d] LSE RECRUITMENT 2016 - EPITA\n", pid); 18 | fprintf(OUT, "[%d] P1kachu 2015 - my_memcheck %s\n", pid, VERSION); 19 | fprintf(OUT, "[%d] Command: %s\n", pid, name.c_str()); 20 | PID(pid); 21 | PID(pid); 22 | 23 | while (1) 24 | { 25 | ptrace(PTRACE_CONT, pid, 0, 0); 26 | 27 | waitpid(pid, &status, 0); 28 | 29 | struct user_regs_struct regs; 30 | ptrace(PTRACE_GETREGS, pid, 0, ®s); 31 | long long addr = regs.XIP; 32 | 33 | auto bp = (void*)(addr - 1); 34 | 35 | if (WIFEXITED(status)) 36 | break; 37 | 38 | if (WIFSIGNALED(status)) 39 | break; 40 | 41 | if (status == 1151) 42 | { 43 | fprintf(OUT, 44 | "[%d] %sIllegal instruction%s - killing child.\n", 45 | pid, PRED, NONE); 46 | break; 47 | } 48 | 49 | // Segfault 50 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) 51 | { 52 | handle_injected_sigsegv(pid, t); 53 | continue; 54 | } 55 | try 56 | { 57 | if (!addr) 58 | break; 59 | 60 | if (!b.is_from_us(bp)) 61 | { 62 | ptrace(PTRACE_GETREGS, pid, ®s, ®s); 63 | regs.XIP++; 64 | ptrace(PTRACE_SETREGS, pid, ®s, ®s); 65 | continue; 66 | } 67 | long syscall = NO_SYSCALL; 68 | 69 | if (bp != b.rr_brk) 70 | syscall = get_xax(pid); 71 | 72 | handle_injected_syscall(syscall, b, bp, t); 73 | 74 | } 75 | catch (std::logic_error) { break; } 76 | } 77 | 78 | ptrace(PTRACE_CONT, pid, 0, 0); 79 | display_memory_leaks(t); 80 | return 0; 81 | } 82 | 83 | 84 | int main(int argc, char** argv) 85 | { 86 | 87 | if (argc < 2) 88 | { 89 | fprintf(OUT, "Usage: %s[--preload lib] binary_to_trace[ARGS]\n", argv[0]); 90 | return 0; 91 | } 92 | 93 | std::string name = argv[1]; 94 | 95 | char* preload = get_cmd_opt(argv, argv + argc, "--preload"); 96 | 97 | if (preload) 98 | { 99 | if (argc > 3) 100 | name = argv[3]; 101 | else 102 | { 103 | fprintf(OUT, "%sERROR:%s No file specified\n", 104 | RED, NONE); 105 | exit(-1); 106 | } 107 | 108 | } 109 | else 110 | { 111 | if (!binary_exists(name) && name.find("--") != std::string::npos) 112 | { 113 | fprintf(OUT, "%sERROR:%s Invalid command option (%s)\n", 114 | RED, NONE, name.c_str()); 115 | exit(-1); 116 | } 117 | } 118 | 119 | if (!binary_exists(name) && name.find("./") != std::string::npos) 120 | { 121 | fprintf(OUT, "%sERROR:%s Binary %s not found.\n", 122 | RED, NONE, name.c_str()); 123 | exit(-1); 124 | } 125 | 126 | pid_t pid = 0; 127 | 128 | if ((pid = fork()) != 0) 129 | return mem_checker(name, pid); 130 | 131 | return run_child(argc - 1, argv + 1, preload); 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/level4/sanity_check.cc: -------------------------------------------------------------------------------- 1 | #include "level4.hh" 2 | 3 | 4 | // Because fuck C++ iostreams 5 | static inline void invalid_memory_access(void* fault, pid_t pid, int size) 6 | { 7 | if (size) 8 | fprintf(OUT, 9 | "[%d] %sInvalid memory access%s of size %d at address %p\n", 10 | pid, PRED, NONE, size, fault); 11 | else 12 | fprintf(OUT, 13 | "[%d] %sInvalid memory access%s of unknown size at address %p\n", 14 | pid, PRED, NONE, fault); 15 | } 16 | 17 | static inline void invalid_memory_write(void* fault, pid_t pid, int size) 18 | { 19 | if (size) 20 | fprintf(OUT, 21 | "[%d] %sInvalid memory write%s of size %d at address %p\n", 22 | pid, PRED, NONE, size, fault); 23 | else 24 | fprintf(OUT, 25 | "[%d] %sInvalid memory write%s of unkown size at address %p\n", 26 | pid, PRED, NONE, fault); 27 | } 28 | 29 | static inline void invalid_memory_read(void* fault, pid_t pid, int size) 30 | { 31 | if (size) 32 | fprintf(OUT, 33 | "[%d] %sInvalid memory read%s of size %d at address %p\n", 34 | pid, PRED, NONE, size, fault); 35 | else 36 | fprintf(OUT, 37 | "[%d] %sInvalid memory read%s of unkown size at address %p\n", 38 | pid, PRED, NONE, fault); 39 | } 40 | 41 | static inline void invalid_free_aux(void* fault, pid_t pid, void* pointer) 42 | { 43 | fprintf(OUT, 44 | "[%d] %sInvalid free%s of pointer %p at address %p\n", 45 | pid, PRED, NONE, pointer, fault); 46 | } 47 | 48 | static bool is_valid(void* fault, Tracker& t, int si_code) 49 | { 50 | UNUSED(si_code); 51 | if (fault == nullptr || si_code != SEGV_ACCERR) 52 | return true; 53 | 54 | auto it = t.get_mapped(reinterpret_cast (fault)); 55 | if (it == t.mapped_areas.end()) 56 | return false; 57 | return true; 58 | } 59 | 60 | static int get_size(char* instru) 61 | { 62 | std::string s(instru); 63 | if (s.find("qword") != std::string::npos) 64 | return sizeof (long); 65 | else if (s.find("dword") != std::string::npos) 66 | return sizeof (int); 67 | else if (s.find("word") != std::string::npos) 68 | return sizeof (short); 69 | else if (s.find("byte") != std::string::npos) 70 | return sizeof (char); 71 | 72 | return 0; 73 | } 74 | 75 | static int get_instruction(pid_t pid, 76 | unsigned long xip, 77 | unsigned long long opcodes, 78 | bool print, 79 | bool segfault, 80 | void* fault) 81 | { 82 | csh handle; 83 | cs_insn* insn = NULL; 84 | size_t count = 0; 85 | int ret = 0; 86 | unsigned char buffer[16] = { 0 }; 87 | 88 | for (int i = 0; i < 8; ++i) 89 | buffer[i] = (opcodes >> (8 * i)) & 0xFF; 90 | 91 | 92 | if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) 93 | return -(printf("CS_OPEN BUG\n")); 94 | 95 | cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); 96 | 97 | count = cs_disasm(handle, buffer, 16, xip, 0, &insn); 98 | 99 | if (count > 0) 100 | { 101 | if (print) 102 | { 103 | UNUSED(segfault); 104 | int size = get_size(insn[0].op_str); 105 | 106 | /* 107 | ** Will be Intel syntax because it's easier 108 | ** to catch access sizes 109 | **/ 110 | 111 | int write = insn[0].detail->regs_write_count; 112 | int read = insn[0].detail->regs_read_count; 113 | 114 | // Read and write, or none (stewpid) 115 | if (/*(write && read) ||*/ (!write && !read)) 116 | invalid_memory_access(fault, pid, size); 117 | 118 | // Invalid read 119 | else if (read) 120 | invalid_memory_read(fault, pid, size); 121 | 122 | // Invalid write 123 | else 124 | invalid_memory_write(fault, pid, size); 125 | 126 | printf("[%d] \t0x%012lx: ", pid, xip); 127 | 128 | for (int i = 0; i < 8; ++i) 129 | printf("%02x ", buffer[i]); 130 | 131 | printf(" %s %s\033[0m\n", 132 | insn[0].mnemonic, insn[0].op_str); 133 | PID(pid); 134 | } 135 | ret = insn[0].size; 136 | cs_free(insn, count); 137 | } 138 | cs_close(& handle); 139 | 140 | return ret; 141 | 142 | } 143 | 144 | int invalid_free(pid_t pid, void* pointer, Tracker& t) 145 | { 146 | struct user_regs_struct regs; 147 | ptrace(PTRACE_GETREGS, pid, ®s, ®s); 148 | unsigned long long instruction_p = ptrace(PTRACE_PEEKDATA, 149 | pid, regs.XIP, 150 | sizeof (unsigned long long)); 151 | siginfo_t infos; 152 | ptrace(PTRACE_GETSIGINFO, pid, 0, &infos); 153 | 154 | void* fault = infos.si_addr; 155 | invalid_free_aux(fault, pid, pointer); 156 | get_instruction(pid, regs.XIP, instruction_p, true, false, fault); 157 | display_memory_leaks(t); 158 | exit(-1); 159 | 160 | } 161 | 162 | int sanity_customs(pid_t pid, Tracker& t, int handler) 163 | { 164 | struct user_regs_struct regs; 165 | ptrace(PTRACE_GETREGS, pid, ®s, ®s); 166 | unsigned long long instruction_p = ptrace(PTRACE_PEEKDATA, 167 | pid, regs.XIP, 168 | sizeof (unsigned long long)); 169 | siginfo_t infos; 170 | ptrace(PTRACE_GETSIGINFO, pid, 0, &infos); 171 | 172 | void* fault = infos.si_addr; 173 | 174 | 175 | int status = 0; 176 | if (handler == SEGFAULT) 177 | { 178 | int size = get_instruction(pid, regs.XIP, instruction_p, true, true, fault); 179 | fprintf(OUT, "[%d] Signal 11 caught (SIGSEGV)", pid); 180 | regs.XIP += size + 1; 181 | ptrace(PTRACE_SETREGS, pid, 0, ®s); 182 | return 0; 183 | } 184 | if (is_valid(fault, t, infos.si_code)) 185 | status = 1; 186 | 187 | 188 | if (!status) 189 | get_instruction(pid, regs.XIP, instruction_p, true, false, fault); 190 | 191 | // printf("Status : %d\n\tXIP : %p\n\tFAULT: %p\n", status, (void*)regs.XIP, fault); 192 | 193 | infos.si_addr = NULL; 194 | 195 | ptrace(PTRACE_SETSIGINFO, pid, 0, &infos); 196 | return 1; 197 | } 198 | 199 | int display_memory_leaks(Tracker& t) 200 | { 201 | unsigned long long heap_sum = 0; 202 | unsigned long long leak_sum = 0; 203 | int heap = 0; 204 | int blocks = 0; 205 | int length = 0; 206 | 207 | for (auto it = t.mapped_areas.begin(); it != t.mapped_areas.end(); it++) 208 | { 209 | int n = snprintf(nullptr, 0, "%ld", it->mapped_length); 210 | length = n > length ? n : length; 211 | blocks++; 212 | if (it->mapped_protections == MALLOC_CHILD) 213 | { 214 | heap_sum += it->mapped_length; 215 | ++heap; 216 | } 217 | leak_sum += it->mapped_length; 218 | 219 | } 220 | 221 | PID(t.pid); 222 | fprintf(OUT, "[%d] Heap leaks: %lld byte(s) in %d block(s)\n", 223 | t.pid, heap_sum, heap); 224 | 225 | if (heap_sum) 226 | { 227 | fprintf(OUT, "[%d] \tTotal heap usage: %d alloc(s), %d free(s).\n", 228 | t.pid, t.nb_of_allocs, t.nb_of_frees); 229 | 230 | PID(t.pid); 231 | 232 | fprintf(OUT, 233 | "[%d] Memory leaks: %s0x%llx%s (%lld) byte(s) not freed at exit (%d block(s))\n", 234 | t.pid, leak_sum ? RED : GREEN, leak_sum, NONE, leak_sum, blocks); 235 | } 236 | else 237 | fprintf(OUT, "[%d] \tEach allocated byte on heap was freed, memory clean\n", 238 | t.pid); 239 | 240 | if (!leak_sum) 241 | { 242 | fprintf(OUT, "[%d] \tEach allocated byte was freed, memory clean\n", t.pid); 243 | return 0; 244 | } 245 | 246 | for (auto it = t.mapped_areas.begin(); it != t.mapped_areas.end(); it++) 247 | fprintf(OUT, "[%d] \t* %*.ld bytes at 0x%lx\n", // NOMO 248 | t.pid, length, it->mapped_length, it->mapped_begin); 249 | 250 | return leak_sum; 251 | } 252 | -------------------------------------------------------------------------------- /tests/debug.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "colors.hh" 9 | #include 10 | #include 11 | #include 12 | 13 | void print_errno(FILE* OUT); 14 | void print_errno(FILE* OUT) 15 | { 16 | if (errno) 17 | { 18 | fprintf(OUT, 19 | "%sERROR%s Something went wrong: %s (%s%s%s:%d)\n", 20 | "\033[31;1m", 21 | "\033[0m", 22 | strerror(errno), 23 | "\033[31;1m", 24 | __FILE__, 25 | "\033[0m", 26 | __LINE__); 27 | } 28 | } 29 | 30 | 31 | 32 | int main() 33 | { 34 | 35 | // fprintf(stdout, "%sEntering main%s\n", CYAN, NONE); 36 | 37 | // FILE* OUT = stdout; 38 | 39 | // print_errno(OUT); 40 | 41 | int fd = open("WHATEVER", O_RDWR|O_CREAT, 0666); 42 | 43 | char *mapped = 44 | (char*)mmap(NULL,0x100,PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, fd, 0); 45 | short *mapped2 = 46 | (short*)mmap(NULL,0x100,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS, fd, 0); 47 | int *mapped3 = 48 | (int*)mmap(NULL, 0x100, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, fd, 0); 49 | long *mapped4 = 50 | (long*)mmap(NULL, 0x100, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, fd, 0); 51 | 52 | 53 | print_errno(stdout); 54 | mapped[5] = 10; 55 | printf("<\n"); 56 | mapped[0x80] = mapped[0x100]; 57 | mapped[0x100] = mapped[0x80]; 58 | mapped2[0x80] = mapped2[0x100]; 59 | mapped2[0x100] = mapped2[0x80]; 60 | mapped3[0x80] = mapped3[0x100]; 61 | mapped3[0x100] = mapped3[0x80]; 62 | mapped4[0x80] = mapped4[0x100]; 63 | mapped4[0x100] = mapped4[0x80]; 64 | 65 | printf("/>\n"); 66 | int l = 0; 67 | 68 | char* t = (char*)malloc(90); 69 | 70 | free(t); 71 | 72 | t = (char*)malloc(1); 73 | 74 | printf("<\n"); 75 | 76 | *(t + 2) = l; 77 | *(t + 7) = 5; 78 | mapped[2] = *t; 79 | mapped[45] = *(t + 7); 80 | printf("/>\n"); 81 | t = (char*)realloc(t, 0x180); 82 | 83 | t = (char*)calloc(1, 0x100); 84 | 85 | /* munmap(mapped, 0x100); 86 | munmap(mapped2, 0x100); 87 | munmap(mapped3, 0x100); 88 | munmap(mapped4, 0x100); 89 | */ 90 | free(t); 91 | 92 | 93 | //*/ 94 | /* 95 | char *t = (char*)calloc(1, 0x1000); 96 | 97 | t[3] = 5; 98 | 99 | 100 | // *(t + 2) = 5; 101 | // *(t + 63) = 5; 102 | // fprintf(OUT, "%sINVALID%s\n", CYAN, NONE); 103 | // *(t + 64) = 7; 104 | 105 | free(t); 106 | // fprintf(OUT, "%sFREED%s\n", CYAN, NONE); 107 | // *(t + 64) = 7; 108 | 109 | // t = (char*)malloc(64); 110 | */ 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /utils/repo_cleaner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find . \( \ 4 | -name "._bckp*" \ 5 | -o -name ".*.bckp" \ 6 | -o -name "*.o" \ 7 | -o -name "CMakeCache.txt" \ 8 | -o -name "CMakeFiles" \ 9 | -o -name "cmake_install.cmake" \ 10 | -o -name ".gdb_history" \ 11 | -o -name "peda*" \ 12 | -o -name "CSR*" \ 13 | -o -name "*.a" \ 14 | -o -name "*~" \ 15 | -o -name ".#*" \ 16 | -o -name "build" \ 17 | -o -name "*.so" \ 18 | -o -name "*.out" \ 19 | -o -name "42sh" \ 20 | -o -name "__pycache__" \ 21 | \) -print -exec rm -rf {} \; 22 | exit 0 23 | --------------------------------------------------------------------------------