├── memo ├── heap1.png ├── heap2.png ├── README.md ├── memo_example.py ├── LICENSE ├── heapfuzz.py └── heapfuzz.c /memo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzonerzy/HeapFuzz/HEAD/memo -------------------------------------------------------------------------------- /heap1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzonerzy/HeapFuzz/HEAD/heap1.png -------------------------------------------------------------------------------- /heap2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dzonerzy/HeapFuzz/HEAD/heap2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HeapFuzz 2 | Capture The Flag Binary fuzzer for Heap challanges 3 | 4 | # Screenshot 5 | 6 | **Manual mode** 7 | ![heap1](https://github.com/dzonerzy/HeapFuzz/blob/master/heap1.png "Heap 1") 8 | 9 | **Automatic mode** 10 | ![heap2](https://github.com/dzonerzy/HeapFuzz/blob/master/heap2.png "Heap 2") 11 | 12 | # How to use 13 | Read the example file! 14 | 15 | Bye 16 | -------------------------------------------------------------------------------- /memo_example.py: -------------------------------------------------------------------------------- 1 | """ 2 | Heap CTF binary fuzzer example (memo) by Daniele Linguaglossa 3 | """ 4 | 5 | from heapfuzz import * 6 | 7 | h = HeapFuzz("./memo", preload_lib="./heapfuzz.so") 8 | completed = Input(kind=InputType.CHOICE, send_after="[yes/no]", choice=["yes\0", "no\0"]) 9 | add_data = Input(kind=InputType.STRING, send_after="Data: ", end="\0", after=completed) 10 | init = Input(kind=InputType.CHOICE, choice=["1", "2", "3"], send_after="> ", map_choice=[add_data, SELF(), SELF()]) 11 | add_data2 = Input(kind=InputType.STRING, send_after="Data: ", end="\0", after=init) 12 | completed.add_map_choice([init, add_data2]) 13 | h.start(init) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Daniele Linguaglossa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /heapfuzz.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Daniele Linguagossa 3 | 4 | Heap CTF binary fuzzing made easy 5 | """ 6 | from pwn import * 7 | import random 8 | import struct 9 | import re 10 | import os 11 | 12 | 13 | class Vulnerability(): 14 | vulns = { 15 | '1': 'HEAP WRITE OOB', 16 | '2': 'HEAP READ OOB', 17 | '3': 'FREE NON ALLOC', 18 | '4': 'DOUBLE FREE', 19 | '5': 'USE_AFTER_FREE', 20 | '6': 'SEGMENTATION FAULT' 21 | } 22 | 23 | def __init__(self, data): 24 | data = data.split("-") 25 | self.kind = data[0] 26 | self.addr = data[1] 27 | self.orgsize = data[2] 28 | self.newsize = data[3] 29 | 30 | def __str__(self): 31 | return "Found {} on {} size: {} new size: {}".format(self.vulns[self.kind], self.addr, self.orgsize, 32 | self.newsize) 33 | 34 | 35 | class SELF(): 36 | pass 37 | 38 | 39 | class InputType(): 40 | STRING = 1 41 | NUMBER = 2 42 | FORMAT = 3 43 | CHOICE = 4 44 | 45 | 46 | class ProcessRestart(): 47 | pass 48 | 49 | 50 | class Input(): 51 | format_re = re.compile('(%[a-z])') 52 | string_charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 53 | 54 | def __init__(self, kind=None, choice=None, min=1, max=10, send_after=">", format=None, newline=True, end="\n", 55 | after=None, map_choice=None): 56 | self.type = kind 57 | self.choice = choice 58 | self.send_after = send_after 59 | self.format = format 60 | self.newline = newline 61 | self.end = end 62 | self.after = after 63 | self.map_choice = map_choice 64 | self.max = max 65 | self.min = min 66 | 67 | def _send(self, data, newline, callback): 68 | callback(data, newline) 69 | try: 70 | if newline: 71 | self.process.sendline(data) 72 | else: 73 | self.process.send_raw(data) 74 | except: 75 | pass 76 | 77 | def _read_until(self): 78 | try: 79 | self.process.readuntil(self.send_after) 80 | except: 81 | pass 82 | 83 | def _apply_post_hook(self, data): 84 | if not self.newline: 85 | data += self.end 86 | return data 87 | 88 | def _random_string(self, post_hook=True): 89 | s = "" 90 | l = random.randint(self.min, self.max) 91 | for i in range(0, l): 92 | s += self.string_charset[random.randint(0, len(self.string_charset) - 1)] 93 | if post_hook: 94 | return self._apply_post_hook(s) 95 | else: 96 | return s 97 | 98 | def _random_int(self, post_hook=True): 99 | n = random.randint(self.min, int(self.max)) 100 | if post_hook: 101 | return self._apply_post_hook(str(n)) 102 | else: 103 | return str(n) 104 | 105 | def _random_format(self): 106 | matches = self.format_re.findall(self.format) 107 | data = self.format 108 | for match in matches: 109 | if match == "%s": 110 | data = data.replace(match, self._random_string(post_hook=False), 1) 111 | else: 112 | data = str(data).replace(match, self._random_int(post_hook=False), 1) 113 | return self._apply_post_hook(data) 114 | 115 | def add_map_choice(self, map_choice): 116 | self.map_choice = map_choice 117 | 118 | def add_after(self, after): 119 | self.after = after 120 | 121 | def run(self, process, callback): 122 | poll = process.poll() 123 | if poll != None: 124 | process.close() 125 | return ProcessRestart() 126 | self.process = process 127 | self._read_until() 128 | if self.type == InputType.STRING: 129 | self._send(self._random_string(), self.newline, callback) 130 | return self.after 131 | elif self.type == InputType.CHOICE: 132 | if self.choice is not None: 133 | idx = random.randint(0, len(self.choice) - 1) 134 | self._send(self.choice[idx], self.newline, callback) 135 | if isinstance(self.map_choice[idx], SELF): 136 | return self 137 | else: 138 | return self.map_choice[idx] 139 | elif self.type == InputType.NUMBER: 140 | self._send(self._random_int(), self.newline, callback) 141 | return self.after 142 | elif self.type == InputType.FORMAT: 143 | self._send(self._random_format(), self.newline, callback) 144 | return self.after 145 | 146 | class HeapFuzz(): 147 | def __init__(self, bin, pipe="/tmp/heapfuzz", preload_lib="./heapfuzz.so"): 148 | self.preload_lib = preload_lib 149 | self._configure() 150 | self.process = process(bin) 151 | self.pipe_name = pipe 152 | self.bin = bin 153 | self._open_pipe() 154 | self.vulnerabilities = {} 155 | self.trigger = [] 156 | 157 | def _configure(self): 158 | with open('/proc/sys/kernel/randomize_va_space', 'r') as aslr: 159 | enabled = int(aslr.read()) 160 | if enabled: 161 | log.warn("Please disable ASLR with 'echo 0 | sudo tee /proc/sys/kernel/randomize_va_space'!") 162 | sys.exit(0) 163 | aslr.close() 164 | context.log_level = "warn" 165 | os.environ["LD_PRELOAD"] = self.preload_lib 166 | os.environ["USE_HEAPFUZZ"] = "1" 167 | 168 | def _open_pipe(self): 169 | self.pipe_fd = os.open(self.pipe_name, os.O_RDONLY | os.O_NONBLOCK) 170 | 171 | def _restart(self): 172 | try: 173 | self.process.close() 174 | except: pass 175 | self.process = process(self.bin) 176 | os.close(self.pipe_fd) 177 | self._open_pipe() 178 | self.trigger = [] 179 | 180 | def _read_from_pipe(self): 181 | try: 182 | l = os.read(self.pipe_fd, 4) 183 | length = struct.unpack(" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | /* 13 | * Author: Daniele Linguaglossa 14 | * 15 | * Please compile with gcc -shared -fPIC -o heapfuzz.so heapfuzz.c -ldl 16 | * then use LD_PRELOAD=./heapfuzz.so and USE_HEAPFUZZ=1 to run your binary 17 | */ 18 | 19 | #define ANSI_COLOR_RED "\x1b[31m" 20 | #define ANSI_COLOR_GREEN "\x1b[32m" 21 | #define ANSI_COLOR_YELLOW "\x1b[33m" 22 | #define ANSI_COLOR_BLUE "\x1b[34m" 23 | #define ANSI_COLOR_MAGENTA "\x1b[35m" 24 | #define ANSI_COLOR_CYAN "\x1b[36m" 25 | #define ANSI_COLOR_RESET "\x1b[0m" 26 | 27 | enum Overflow { 28 | HEAP_WRITE_OOB=1, 29 | HEAP_READ_OOB=2, 30 | FREE_NON_ALLOC=3, 31 | DOUBLE_FREE=4, 32 | USE_AFTER_FREE=5, 33 | SEGMENTATION_FAULT = 6, 34 | }; 35 | 36 | struct allocated_area { 37 | void * ptr; 38 | size_t size; 39 | void * endaddr; 40 | void * rw_page; 41 | void * none_page; 42 | int free; 43 | }; 44 | 45 | char * IPC_NAME = "/tmp/heapfuzz"; 46 | int IPC_FD = -1; 47 | int idx = 0; 48 | int area_size = 0; 49 | struct allocated_area areas[1024]; 50 | 51 | static char* (*real_strcpy)(char * dst, const char * src)=NULL; 52 | static void (*real_free)(void *ptr)=NULL; 53 | static void* (*real_malloc)(size_t)=NULL; 54 | static void* (*real_calloc)(size_t nitems, size_t size)=NULL; 55 | static void* (*real_realloc)(void *ptr, size_t size)=NULL; 56 | static int (*real__libc_start_main)(int (*main) (int,char **,char **),int argc,char **ubp_av,void (*init) (void),void (*fini)(void),void (*rtld_fini)(void),void (*stack_end)) = NULL; 57 | static ssize_t (*real_read)(int, void*, size_t)=NULL; 58 | 59 | static void mtrace_init(void) 60 | { 61 | int err = 0; 62 | real_malloc = dlsym(RTLD_NEXT, "malloc"); 63 | real_free = dlsym(RTLD_NEXT, "free"); 64 | real_strcpy = dlsym(RTLD_NEXT, "strcpy"); 65 | real_calloc = dlsym(RTLD_NEXT, "calloc"); 66 | real_realloc = dlsym(RTLD_NEXT, "realloc"); 67 | real_strcpy = dlsym(RTLD_NEXT, "strcpy"); 68 | real_read = dlsym(RTLD_NEXT, "read"); 69 | real__libc_start_main = dlsym(RTLD_NEXT,"__libc_start_main"); 70 | 71 | if (NULL == real_malloc) { 72 | fprintf(stderr, "Error in `dlsym(malloc)`: %s\n", dlerror()); 73 | err = 1; 74 | }else if(NULL == real_free) { 75 | fprintf(stderr, "Error in `dlsym(free)`: %s\n", dlerror()); 76 | err = 1; 77 | }else if(NULL == real_strcpy) { 78 | fprintf(stderr, "Error in `dlsym(strcpy)`: %s\n", dlerror()); 79 | err = 1; 80 | }else if(NULL == real_calloc) { 81 | fprintf(stderr, "Error in `dlsym(calloc)`: %s\n", dlerror()); 82 | err = 1; 83 | }else if(NULL == real_realloc) { 84 | fprintf(stderr, "Error in `dlsym(realloc)`: %s\n", dlerror()); 85 | err = 1; 86 | }else if(NULL == real_strcpy) { 87 | fprintf(stderr, "Error in `dlsym(strcpy)`: %s\n", dlerror()); 88 | err = 1; 89 | }else if(NULL == real__libc_start_main) { 90 | fprintf(stderr, "Error in `dlsym(__libc_start_main)`: %s\n", dlerror()); 91 | err = 1; 92 | } 93 | 94 | if( err ){ 95 | exit(-1); 96 | } 97 | } 98 | 99 | static void handler(int sig, siginfo_t *si, void *context) 100 | { 101 | ucontext_t *u = (ucontext_t *)context; 102 | for(int i=0; isi_addr < areas[i].none_page && si->si_addr >= areas[i].rw_page) 105 | { 106 | if(areas[i].free) 107 | { 108 | display_vuln(USE_AFTER_FREE, si->si_addr, 0, 0); 109 | } 110 | 111 | if(u->uc_mcontext.gregs[REG_ERR] & 0x2){ 112 | display_vuln(HEAP_WRITE_OOB, si->si_addr, 0, 0); 113 | }else{ 114 | display_vuln(HEAP_READ_OOB, si->si_addr, 0, 0); 115 | } 116 | } 117 | } 118 | 119 | if(u->uc_mcontext.gregs[REG_ERR] & 0x2){ 120 | display_vuln(SEGMENTATION_FAULT, si->si_addr, 0, 0); 121 | }else{ 122 | display_vuln(SEGMENTATION_FAULT, si->si_addr, 0, 0); 123 | } 124 | } 125 | 126 | 127 | int get_area_index(void *ptr) 128 | { 129 | for(int i=0; i= 0) 147 | { 148 | if(areas[index].size < nbyte) 149 | { 150 | display_vuln(HEAP_WRITE_OOB, buf, areas[index].size, nbyte); 151 | }else{ 152 | ssize_t s = real_read(fildes, buf, nbyte); 153 | return s; 154 | } 155 | }else{ 156 | ssize_t s = real_read(fildes, buf, nbyte); 157 | return s; 158 | } 159 | } 160 | 161 | void * map(size_t s , int prot){ 162 | void *ptr = mmap(0, s, prot, MAP_PRIVATE | MAP_ANON, -1, 0); 163 | if (map == MAP_FAILED) { 164 | perror("Error mmapping the file"); 165 | exit(-1); 166 | } 167 | return ptr; 168 | } 169 | 170 | void * add_area(int * index, size_t size) 171 | { 172 | 173 | areas[*index].none_page = map(size, PROT_NONE); 174 | areas[*index].rw_page = map(size, PROT_READ|PROT_WRITE); 175 | areas[*index].ptr = areas[*index].none_page - size; 176 | areas[*index].size = size; 177 | areas[*index].free = 0; 178 | area_size++; 179 | return areas[*index].ptr; 180 | } 181 | 182 | void free_area(int index) 183 | { 184 | areas[index].free = 1; 185 | if(mprotect(areas[index].rw_page,0x1000, PROT_NONE) != 0) 186 | { 187 | perror("mprotect error!"); 188 | } 189 | } 190 | 191 | 192 | void display_vuln(enum Overflow kind, void * ptr, size_t org_size, size_t new_size) 193 | { 194 | char * estr; 195 | if(kind == HEAP_WRITE_OOB){ 196 | estr="HEAP WRITE OOB"; 197 | }else if(kind == HEAP_READ_OOB){ 198 | estr="HEAP READ OOB"; 199 | }else if(kind == FREE_NON_ALLOC){ 200 | estr="FREE NON ALLOC"; 201 | }else if(kind == DOUBLE_FREE){ 202 | estr="DOUBLE FREE"; 203 | }else if(kind == USE_AFTER_FREE){ 204 | estr="USE AFTER FREE"; 205 | }else if(kind == SEGMENTATION_FAULT){ 206 | estr="SEGMENTATION FAULT"; 207 | } 208 | 209 | char * heapfuzz = getenv("USE_HEAPFUZZ"); 210 | 211 | if(heapfuzz != NULL && strcmp(heapfuzz, "1")==0){ 212 | char cmd[128]; 213 | memset(cmd, 0, sizeof(cmd)); 214 | sprintf(cmd,"%d-%p-%d-%d",kind,ptr,org_size, new_size); 215 | int len = strlen(cmd); 216 | write(IPC_FD,(char *)&len, sizeof(len)); 217 | write(IPC_FD, cmd, strlen(cmd)); 218 | }else{ 219 | fprintf(stderr, "\n" "=================================================================\n" ANSI_COLOR_CYAN "%s" ANSI_COLOR_RESET 220 | " (ptr=" ANSI_COLOR_GREEN "%p" ANSI_COLOR_RESET " buffer_size=" ANSI_COLOR_GREEN "0x%x" ANSI_COLOR_RESET " write_size=" ANSI_COLOR_GREEN "0x%x" ANSI_COLOR_RESET ")\n" "=================================================================" "\n" ANSI_COLOR_RESET, estr, ptr, org_size, new_size 221 | ); 222 | } 223 | exit(-1); 224 | } 225 | 226 | int __libc_start_main(int (*main) (int,char **,char **),int argc,char **ubp_av,void (*init) (void),void (*fini)(void),void (*rtld_fini)(void), 227 | void (*stack_end)) { 228 | 229 | struct sigaction action; 230 | memset(&action, 0, sizeof(struct sigaction)); 231 | action.sa_flags = SA_SIGINFO; 232 | action.sa_sigaction = handler; 233 | sigaction(SIGSEGV, &action, NULL); 234 | 235 | if(real__libc_start_main==NULL) { 236 | mtrace_init(); 237 | } 238 | setvbuf(stderr, NULL, _IONBF, 0); 239 | setvbuf(stdout, NULL, _IONBF, 0); 240 | char * heapfuzz = getenv("USE_HEAPFUZZ"); 241 | if(heapfuzz != NULL && strcmp(heapfuzz, "1")==0){ 242 | char init_message[4]; 243 | mkfifo(IPC_NAME, 0777); 244 | IPC_FD = open(IPC_NAME, O_RDWR); 245 | return real__libc_start_main(main,argc,ubp_av,init,fini,rtld_fini,stack_end); 246 | }else{ 247 | return real__libc_start_main(main,argc,ubp_av,init,fini,rtld_fini,stack_end); 248 | } 249 | } 250 | 251 | 252 | char* strcpy(char * dst, const char * src) 253 | { 254 | if(real_strcpy==NULL) { 255 | mtrace_init(); 256 | } 257 | 258 | char * d = real_strcpy(dst, src); 259 | return d; 260 | } 261 | 262 | void free(void *ptr) 263 | { 264 | if(real_free==NULL) { 265 | mtrace_init(); 266 | } 267 | 268 | int index = get_area_index(ptr); 269 | if(index >=0) 270 | { 271 | if(areas[index].free == 1) 272 | { 273 | display_vuln(DOUBLE_FREE, ptr,0, 0); 274 | }else{ 275 | free_area(index); 276 | } 277 | }else{ 278 | display_vuln(FREE_NON_ALLOC, ptr, 0, 0); 279 | } 280 | 281 | } 282 | 283 | void *malloc(size_t size) 284 | { 285 | if(real_malloc==NULL) { 286 | mtrace_init(); 287 | } 288 | void *p = add_area(&idx, size); 289 | return p; 290 | } 291 | 292 | void *calloc(size_t nitems, size_t size) 293 | { 294 | if(real_calloc==NULL) { 295 | mtrace_init(); 296 | } 297 | 298 | int filled = 0; 299 | void *p = add_arena(&idx, nitems*size); 300 | return p; 301 | } 302 | 303 | void * realloc(void *ptr, size_t size) 304 | { 305 | if(real_realloc==NULL) { 306 | mtrace_init(); 307 | } 308 | 309 | for(int i=0; i