├── demo.png ├── README.md ├── heappo.py └── villoc_mod.py /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uf0o/heappo/HEAD/demo.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Heappo 2 | 3 | **Heappo** 🦛 is a PyKD based extensions for WinDBG which aids Heap Exploitation by logging the followings: 4 | 5 | Tutorial and background [here](https://www.matteomalvica.com/blog/2020/03/24/heappo-windbg-heap-tracing/) 6 | ## Features 7 | 8 | Runs on both Py2/Py3 and x86/x64 9 | Timestamps :) 10 | 11 | ### Tracing: 12 | * RtlAllocateHeap 13 | * RtlReAllocateHeap 14 | * RtlFreeHeap 15 | * VirtualAlloc 16 | 17 | ### Paramenters 18 | * Custom allocation size 19 | * External log file 20 | 21 | ### To do: 22 | * ~~Add VirtualAlloc~~ 23 | * Group functions by same usr-ptr (and possibly same return pointer/caller) 24 | * Log file in mona.py format 25 | 26 | ## Requirements 27 | * Python2.7 OR Python3.6 x64 28 | * PyKD x64/32 29 | * WinDbg :) 30 | 31 | 32 | ## Installation and Setup 33 | From within WinDBG 34 | 35 | .load pykd 36 | !py heappo.py log= 37 | 38 | # Example 39 | !py heappo.py 0x40 log=on 40 | 41 | 42 | ## Sample Outputs 43 | 44 | ``` 45 | 0:001> !py c:\users\matteo\desktop\heappo.py null log=on 46 | 2020-03-25 09:23:48.954000, VirtualAlloc(0x0L , 0x2800000L , 0x2000L , 0x4L) = 0x510000 - From: 0x401034 47 | 2020-03-25 09:24:52.166000, VirtualAlloc(0x0L , 0xa00000L , 0x1000L , 0x4L) = 0x2d10000 - From: 0x40104b 48 | ``` 49 | 50 | ``` 51 | 0:014> !py c:\users\matteo\desktop\heappo.py 0x40 log=on 52 | 2020-03-25 09:26:11.463000, RtlAllocateHeap(0x5c0000L , 0x0L , 0x40L) = 0x6435c8 - From: 0x7124b36e 53 | 2020-03-25 09:26:14.224000, RtlAllocateHeap(0x5c0000L , 0x0L , 0x40L) = 0x6435c8 - From: 0x7124b36e 54 | 2020-03-25 09:26:17.048000, RtlAllocateHeap(0x5c0000L , 0x0L , 0x40L) = 0x6435c8 - From: 0x7124b36e 55 | 2020-03-25 09:26:17.048000, RtlAllocateHeap(0x5c0000L , 0x0L , 0x40L) = 0x6435c8 - From: 0x7124b36e 56 | 2020-03-25 09:26:27.204000, RtlAllocateHeap(0x5c0000L , 0x0L , 0x40L) = 0x642a40 - From: 0x761c0636 57 | ``` 58 | 59 | ## Sample Graph 60 | 61 | ![demo](/demo.png) 62 | 63 | ## Credits 64 | 65 | Greatly inspired by Sam Brown [project](https://labs.f-secure.com/archive/heap-tracing-with-windbg-and-python) 66 | 67 | 68 | -------------------------------------------------------------------------------- /heappo.py: -------------------------------------------------------------------------------- 1 | # .load pykd 2 | # !py c:\users\matteo\desktop\heappo.py 0x40 log=on 3 | 4 | # to do: 5 | # Group functions by same usr-ptr (and same return pointer?) 6 | # Log file in mona format (need to check the specs) 7 | 8 | import pykd 9 | import datetime 10 | from os.path import expanduser 11 | 12 | home = expanduser("~") 13 | return_reg = "rax" 14 | stack_pointer = "rsp" 15 | arch_bits = 64 16 | log = None 17 | 18 | # we need to remove the tick char in case we deal with x64 addr 19 | def format64(address): 20 | address64 = address.replace('`','') 21 | return address64 22 | 23 | def get_address(localAddr): 24 | res = pykd.dbgCommand("x " + localAddr) 25 | result_count = res.count("\n") 26 | if result_count == 0: 27 | print(localAddr + " not found.") 28 | return None 29 | if result_count > 1: 30 | print("[-] Warning, more than one result for", localAddr) 31 | return res.split()[0] 32 | 33 | #RtlAllocateHeap( 34 | # IN PVOID HeapHandle, 35 | # IN ULONG Flags, 36 | # IN ULONG Size ); 37 | class handle_allocate_heap(pykd.eventHandler): 38 | def __init__(self): 39 | addr = format64(get_address("ntdll!RtlAllocateHeap")) 40 | if addr == None: 41 | return 42 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 43 | 44 | def enter_call_back(self): 45 | self.condition = False 46 | time = datetime.datetime.now() 47 | current_alloc_size = (hex(pykd.ptrMWord(pykd.reg("esp") + 0xC))).replace('L','') 48 | if (current_alloc_size == alloc_size) or "null" in alloc_size: 49 | self.condition = True 50 | self.out = str(time) 51 | self.out += ", RtlAllocateHeap(" 52 | if arch_bits == 32: 53 | esp = pykd.reg("esp") 54 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 55 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 56 | self.out += hex(pykd.ptrMWord(esp + 0xC)) + ") = " 57 | else: 58 | self.out += hex(pykd.reg("rcx")) + " , " 59 | self.out += hex(pykd.reg("rdx")) + " , " 60 | self.out += hex(pykd.reg("r8")) + ") = " 61 | if self.condition: 62 | disas = pykd.dbgCommand("uf ntdll!RtlAllocateHeap").split('\n') 63 | for i in disas: 64 | if 'ret' in i: 65 | self.ret_addr = format64(i.split()[0]) 66 | break 67 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 68 | return False 69 | 70 | def return_call_back(self): 71 | if self.condition: 72 | esp = pykd.reg(stack_pointer) 73 | self.out += hex(pykd.reg(return_reg)) 74 | self.out += " - From: " + (hex(pykd.ptrPtr(esp))).replace('L','') 75 | print(self.out) 76 | if logging: 77 | log.write(self.out + "\n") 78 | return False 79 | 80 | #RtlFreeHeap( 81 | #IN PVOID HeapHandle, 82 | #IN ULONG Flags OPTIONAL, 83 | #IN PVOID MemoryPointer ); 84 | class handle_free_heap(pykd.eventHandler): 85 | def __init__(self): 86 | addr = format64(get_address("ntdll!RtlFreeHeap")) 87 | if addr == None: 88 | return 89 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 90 | self.bp_end = None 91 | 92 | def enter_call_back(self): 93 | self.condition = False 94 | time = datetime.datetime.now() 95 | current_free_size = (hex(pykd.ptrMWord(pykd.reg("esp") + 0xC))).replace('L','') 96 | # logging everything except Free[0] 97 | if (current_free_size != "0x0"): 98 | self.condition = True 99 | self.out = str(time) 100 | self.out += ", RtlFreeHeap(" 101 | if arch_bits == 32: 102 | esp = pykd.reg(stack_pointer) 103 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 104 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 105 | self.out += hex(pykd.ptrPtr(esp + 0xC)) + ") = " 106 | else: 107 | self.out += hex(pykd.reg("rcx")) + " , " 108 | self.out += hex(pykd.reg("rdx")) + " , " 109 | self.out += hex(pykd.reg("r8")) + ") = " 110 | if self.bp_end == None: 111 | disas = pykd.dbgCommand("uf ntdll!RtlFreeHeap").split('\n') 112 | for i in disas: 113 | if 'ret' in i: 114 | self.ret_addr = format64(i.split()[0]) 115 | break 116 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 117 | return False 118 | 119 | def return_call_back(self): 120 | #returns a BOOLEAN which is a byte under the hood 121 | if self.condition: 122 | esp = pykd.reg(stack_pointer) 123 | ret_val = hex(pykd.reg("al")) 124 | self.out += ret_val 125 | self.out += " - From: " + (hex(pykd.ptrPtr(esp))).replace('L','') 126 | print(self.out) 127 | if logging: 128 | log.write(self.out + "\n") 129 | return False 130 | 131 | #RtlReAllocateHeap( 132 | #IN PVOID HeapHandle, 133 | #IN ULONG Flags, 134 | # IN PVOID MemoryPointer, 135 | # IN ULONG Size ); 136 | 137 | class handle_realloc_heap(pykd.eventHandler): 138 | def __init__(self): 139 | addr = format64(get_address("ntdll!RtlReAllocateHeap")) 140 | if addr == None: 141 | return 142 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 143 | self.bp_end = None 144 | 145 | def enter_call_back(self): 146 | self.condition = False 147 | time = datetime.datetime.now() 148 | current_alloc_size = (hex(pykd.ptrMWord(pykd.reg("esp") + 0x10))).replace('L','') 149 | if (current_alloc_size == alloc_size) or "null" in alloc_size: 150 | self.condition = True 151 | self.out = str(time) 152 | self.out += ", RtlReAllocateHeap(" 153 | if arch_bits == 32: 154 | esp = pykd.reg(stack_pointer) 155 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 156 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 157 | self.out += hex(pykd.ptrPtr(esp + 0xC)) + " , " 158 | self.out += hex(pykd.ptrMWord(esp + 0x10)) + ") = " 159 | else: 160 | self.out += hex(pykd.reg("rcx")) + " , " 161 | self.out += hex(pykd.reg("rdx")) + " , " 162 | self.out += hex(pykd.reg("r8")) + " , " 163 | self.out += hex(pykd.reg("r9")) + ") = " 164 | if self.bp_end == None: 165 | disas = pykd.dbgCommand("uf ntdll!RtlReAllocateHeap").split('\n') 166 | for i in disas: 167 | if 'ret' in i: 168 | self.ret_addr = format64(i.split()[0]) 169 | break 170 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 171 | return False 172 | 173 | def return_call_back(self): 174 | if self.condition: 175 | esp = pykd.reg(stack_pointer) 176 | self.out += hex(pykd.reg(return_reg)) 177 | self.out += " - From: " + (hex(pykd.ptrPtr(esp))).replace('L','') 178 | print(self.out) 179 | if logging: 180 | log.write(self.out + "\n") 181 | return False 182 | 183 | #VirtualAlloc( 184 | # LPVOID lpAddress, 185 | # SIZE_T dwSize, 186 | # DWORD flAllocationType, 187 | # DWORD flProtect 188 | # ); 189 | 190 | class handle_virtual_alloc(pykd.eventHandler): 191 | def __init__(self): 192 | addr = format64(get_address("kernel32!VirtualAlloc")) 193 | if addr == None: 194 | return 195 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 196 | self.bp_end = None 197 | 198 | def enter_call_back(self): 199 | time = datetime.datetime.now() 200 | self.out = str(time) 201 | self.out += ", VirtualAlloc(" 202 | if arch_bits == 32: 203 | esp = pykd.reg(stack_pointer) 204 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 205 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 206 | self.out += hex(pykd.ptrMWord(esp + 0xC)) + " , " 207 | self.out += hex(pykd.ptrMWord(esp + 0x10)) + ") = " 208 | else: 209 | self.out += hex(pykd.reg("rcx")) + " , " 210 | self.out += hex(pykd.reg("rdx")) + " , " 211 | self.out += hex(pykd.reg("r8")) + " , " 212 | self.out += hex(pykd.reg("r9")) + ") = " 213 | if self.bp_end == None: 214 | disas = pykd.dbgCommand("uf kernelbase!VirtualAlloc").split('\n') 215 | for i in disas: 216 | if 'ret' in i: 217 | self.ret_addr = format64(i.split()[0]) 218 | break 219 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 220 | return False 221 | 222 | def return_call_back(self): 223 | esp = pykd.reg(stack_pointer) 224 | self.out += hex(pykd.reg(return_reg)) 225 | self.out += " - From: " + (hex(pykd.ptrPtr(esp))).replace('L','') 226 | print(self.out) 227 | if logging: 228 | log.write(self.out + "\n") 229 | return False 230 | 231 | def usage(): 232 | print("(*) # from withing WinDBG #") 233 | print("(*) .load pykd") 234 | print("(*) !py heappo log= \n\n") 235 | print("(*) Example:\n") 236 | print("(*) !py heappo 0x40 log=on") 237 | print("(*) To log any allocation just set heap_alloc_size as 'null'") 238 | 239 | 240 | if len(sys.argv) < 3: 241 | usage() 242 | sys.exit() 243 | 244 | log = open(home + "\log.log","w+") 245 | logging = False 246 | alloc_size = sys.argv[1] 247 | log_var = sys.argv[2] 248 | 249 | if "on" in log_var: 250 | logging = True 251 | 252 | try: 253 | pykd.reg("rax") 254 | except: 255 | arch_bits = 32 256 | return_reg = "eax" 257 | stack_pointer = "esp" 258 | 259 | handle_allocate_heap() 260 | handle_free_heap() 261 | handle_realloc_heap() 262 | handle_virtual_alloc() 263 | pykd.go() 264 | 265 | -------------------------------------------------------------------------------- /villoc_mod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # @wapiflapi 3 | 4 | import re 5 | import random 6 | import codecs 7 | 8 | html_escape_table = { 9 | "&": "&", 10 | '"': """, 11 | "'": "'", 12 | ">": ">", 13 | "<": "<", 14 | } 15 | 16 | def html_escape(text): 17 | """Produce entities within text.""" 18 | return "".join(html_escape_table.get(c,c) for c in text) 19 | 20 | class State(list): 21 | 22 | def __init__(self, *args, **kwargs): 23 | self.errors = [] 24 | self.info = [] 25 | list.__init__(self,*args, **kwargs) 26 | 27 | def boundaries(self): 28 | bounds = set() 29 | for block in self: 30 | lo, hi = block.boundaries() 31 | bounds.add(lo) 32 | bounds.add(hi) 33 | return bounds 34 | 35 | 36 | class Printable(): 37 | 38 | unit_width = 10 39 | classes = ["block"] 40 | 41 | def boundaries(self): 42 | return (self.start(), self.end()) 43 | 44 | def gen_html(self, out, width, color=""): 45 | out.write('
' % 46 | (" ".join(self.classes), 10 * width, color)) 47 | if self.details: 48 | out.write('%#x
' % self.start()) 49 | out.write(self.more_html()) 50 | else: 51 | out.write(' ') 52 | 53 | out.write('
\n') 54 | 55 | def more_html(self): 56 | return "" 57 | 58 | def __repr__(self): 59 | return "%s(start=%#x, end=%#x)" % (self.__class__.__name__, 60 | self.start(), self.end()) 61 | 62 | 63 | class Empty(Printable): 64 | 65 | classes = Printable.classes + ["empty"] 66 | 67 | def __init__(self, start, end, display=True): 68 | self._start = start 69 | self._end = end 70 | self.details = display 71 | 72 | def start(self): 73 | return self._start 74 | 75 | def end(self): 76 | return self._end 77 | 78 | def set_end(self, end): 79 | self._end = end 80 | 81 | def more_html(self): 82 | return "+ %#x" % (self.end() - self.start()) 83 | 84 | 85 | class Block(Printable): 86 | 87 | header = 8 88 | footer = 0 89 | round = 0x10 90 | minsz = 0x20 91 | 92 | classes = Printable.classes + ["normal"] 93 | 94 | def __init__(self, addr, size, error=False, tmp=False, **kwargs): 95 | self.color = kwargs.get('color', random_color()) 96 | self.uaddr = addr 97 | self.usize = size 98 | self.details = True 99 | self.error = error 100 | self.tmp = tmp 101 | self.end_addr = None 102 | self.start_addr = None 103 | 104 | def start(self): 105 | if not self.start_addr: 106 | self.start_addr = self.uaddr - self.header 107 | return self.start_addr 108 | 109 | def end(self): 110 | if not self.end_addr: 111 | size = max(self.minsz, self.usize + self.header + self.footer) 112 | rsize = size + (self.round - 1) 113 | rsize = rsize - (rsize % self.round) 114 | self.end_addr = self.uaddr - self.header + rsize 115 | return self.end_addr 116 | 117 | def gen_html(self, out, width): 118 | 119 | if self.color: 120 | color = ("background-color: rgb(%d, %d, %d);" % self.color) 121 | else: 122 | color = "" 123 | 124 | if self.error: 125 | color += ("background-image: repeating-linear-gradient(" 126 | "120deg, transparent, transparent 1.40em, " 127 | "#A85860 1.40em, #A85860 2.80em);") 128 | 129 | Printable.gen_html(self,out, width, color) 130 | 131 | def more_html(self): 132 | return "+ %#x (%#x)" % (self.end() - self.start(), self.usize) 133 | 134 | def __repr__(self): 135 | return "%s(start=%#x, end=%#x, tmp=%s)" % ( 136 | self.__class__.__name__, self.start(), self.end(), self.tmp) 137 | 138 | 139 | class Marker(Block): 140 | 141 | def __init__(self, addr, error=False, **kwargs): 142 | Block.__init__(self,addr, 0x0, tmp=True, error=error, *kwargs) 143 | 144 | def more_html(self): 145 | return "unknown" 146 | 147 | 148 | def match_ptr(state, ptr): 149 | 150 | if ptr is None: 151 | return None, None 152 | 153 | s, smallest_match = None, None 154 | 155 | for i, block in enumerate(state): 156 | if block.uaddr != ptr: 157 | continue 158 | if smallest_match is None or smallest_match.usize >= block.usize: 159 | s, smallest_match = i, block 160 | 161 | if smallest_match is None: 162 | state.errors.append("Couldn't find block at %#x, added marker." % 163 | (ptr - Block.header)) 164 | # We'll add a small tmp block here to show the error. 165 | state.append(Marker(ptr, error=True)) 166 | 167 | return s, smallest_match 168 | 169 | def RtlAllocateHeap(state, ret, heap, flags, size): 170 | if not ret: 171 | state.errors.append("Failed to allocate %#x bytes." % size) 172 | else: 173 | state.append(Block(ret, size)) 174 | 175 | def RtlFreeHeap(state, ret, heap,flags, ptr): 176 | if ptr is 0: 177 | return 178 | 179 | s, match = match_ptr(state, ptr) 180 | 181 | if match is None: 182 | return 183 | elif ret is None: 184 | state[s] = Block(match.uaddr, match.usize, 185 | error=True, color=match.color) 186 | else: 187 | del state[s] 188 | 189 | def RtlReallocateHeap(state, ret, heap, flags, ptr, size): 190 | 191 | if not ptr: 192 | return malloc(state, ret, size) 193 | elif not size: 194 | return free(state, ret, ptr) 195 | 196 | s, match = match_ptr(state, ptr) 197 | 198 | if match is None: 199 | return 200 | elif ret is None: 201 | state[s] = Block(match.uaddr, match.usize, color=match.color) 202 | state[s].error = True 203 | else: 204 | state[s] = Block(ret, size, color=match.color) 205 | 206 | operations = { 207 | 'RtlAllocateHeap': RtlAllocateHeap, 208 | 'RtlFreeHeap': RtlFreeHeap, 209 | 'RtlReallocateHeap': RtlReallocateHeap 210 | } 211 | 212 | def sanitize(x): 213 | x = x.strip() 214 | if x is None: 215 | return None 216 | if x == "": 217 | return 0 218 | if x.endswith('L'): 219 | x = x[:-1] 220 | return int(x, 16) 221 | 222 | 223 | def parse_ltrace(ltrace): 224 | 225 | match_call = re.compile(r"^([0-9-:.... ]+), ([a-zA-Z_]+)\((.*)\) += (.*)- +From: (.*)") 226 | match_err = re.compile(r"^([a-zA-Z_]+)\((.*) ") 227 | 228 | for line in ltrace: 229 | 230 | # if the trace file contains PID (for ltrace -f) 231 | head, _, tail = line.partition(" ") 232 | if head.isdigit(): 233 | line = tail 234 | 235 | #if not any(line.startswith(f) for f in operations): 236 | # print("skipping") 237 | # continue 238 | 239 | try: 240 | timestamp,func, args, ret, caller = match_call.findall(line)[0] 241 | except Exception: 242 | 243 | try: 244 | # maybe this stopped the program 245 | print("excepting") 246 | func, args = match_err.findall(line)[0] 247 | ret = None 248 | except Exception as (e): 249 | print(e) 250 | print("ignoring line: %s" % line) 251 | continue 252 | 253 | args = map(sanitize, args.split(", ")) 254 | ret = sanitize(ret) 255 | 256 | yield timestamp, func, args, ret, caller 257 | 258 | 259 | def build_timeline(events): 260 | 261 | boundaries = set() 262 | timeline = [State()] 263 | 264 | for timestamp, func, args, ret, caller in events: 265 | 266 | try: 267 | op = operations[func] 268 | except KeyError: 269 | continue 270 | 271 | state = State(filter(lambda x: not x.tmp, timeline[-1])) 272 | 273 | call = "%s(%s)" % (func, ", ".join("%#x" % a for a in args)) 274 | if ret is None: 275 | state.errors.append("%s = " % call) 276 | else: 277 | state.info.append("%s - %s = %#x From: %s" % (timestamp, call, ret,caller)) 278 | 279 | op(state, ret, *args) 280 | boundaries.update(state.boundaries()) 281 | timeline.append(state) 282 | 283 | return timeline, boundaries 284 | 285 | 286 | def random_color(r=200, g=200, b=125): 287 | 288 | red = (random.randrange(0, 256) + r) / 2 289 | green = (random.randrange(0, 256) + g) / 2 290 | blue = (random.randrange(0, 256) + b) / 2 291 | 292 | return (red, green, blue) 293 | 294 | 295 | def print_state(out, boundaries, state): 296 | 297 | out.write('
\n' % ("error" if state.errors else "")) 298 | 299 | known_stops = set() 300 | 301 | todo = {x.start():x for x in state} 302 | while todo: 303 | 304 | out.write('
\n') 305 | 306 | done = set() 307 | 308 | current = None 309 | last = 0 310 | 311 | for i, b in enumerate(boundaries): 312 | 313 | # If this block has size 0; make it continue until the 314 | # next boundary anyway. The size will be displayed as 315 | # 0 or unknown anyway and it shouldn't be too confusing. 316 | if current and current.end() != b and current.start() != current.end(): 317 | continue 318 | 319 | if current: # stops here. 320 | known_stops.add(i) 321 | current.gen_html(out, i - last) 322 | done.add(current) 323 | last = i 324 | 325 | current = None 326 | try: 327 | current = todo[b] 328 | except: 329 | continue 330 | 331 | if last != i: 332 | 333 | # We want to show from previous known_stop. 334 | 335 | for s in reversed(range(last, i+1)): 336 | if s in known_stops: 337 | break 338 | 339 | if s != last: 340 | Empty(boundaries[last], boundaries[s], 341 | display=False).gen_html(out, s - last) 342 | known_stops.add(s) 343 | 344 | if s != i: 345 | Empty(boundaries[s], b).gen_html(out, i - s) 346 | known_stops.add(i) 347 | 348 | last = i 349 | 350 | 351 | if current: 352 | raise RuntimeError("Block was started but never finished.") 353 | 354 | if not done: 355 | raise RuntimeError("Some block(s) don't match boundaries.") 356 | 357 | out.write('
\n') 358 | 359 | todo = {x.start():x for x in todo.values() if x not in done} 360 | 361 | out.write('
') 362 | 363 | for msg in state.info: 364 | out.write('

%s

' % html_escape(str(msg))) 365 | 366 | for msg in state.errors: 367 | out.write('

%s

' % html_escape(str(msg))) 368 | 369 | out.write('
\n') 370 | 371 | out.write('
\n') 372 | 373 | 374 | def gen_html(timeline, boundaries, out): 375 | 376 | if timeline and not timeline[0]: 377 | timeline.pop(0) 378 | 379 | boundaries = sorted(boundaries) 380 | 381 | out.write('\n') 460 | 461 | out.write('') 462 | out.write(''' 474 | ''') 475 | 476 | out.write('\n') 477 | 478 | out.write('
\n') 479 | 480 | for state in timeline: 481 | print_state(out, boundaries, state) 482 | 483 | out.write('
\n') 484 | 485 | out.write('\n') 486 | 487 | 488 | if __name__ == '__main__': 489 | 490 | import sys 491 | import argparse 492 | 493 | parser = argparse.ArgumentParser() 494 | parser.add_argument("ltrace", type=argparse.FileType("rb")) 495 | parser.add_argument("out", type=argparse.FileType("w",50000000)) 496 | parser.add_argument("--header", type=int, default=8, 497 | help="size of malloc metadata before user data") 498 | parser.add_argument("--footer", type=int, default=0, 499 | help="size of malloc metadata after user data") 500 | parser.add_argument("--round", type=int, default=0x08, 501 | help="size of malloc chunks are a multiple of this value") 502 | parser.add_argument("--minsz", type=int, default=0x08, 503 | help="size of a malloc chunk is at least this value") 504 | parser.add_argument("--raw", action="store_true", 505 | help="disables header, footer, round and minsz") 506 | 507 | # Some values that work well: 38, 917, 190, 226 508 | parser.add_argument("-s", "--seed", type=int, default=226) 509 | parser.add_argument("-S", "--show-seed", action="store_true") 510 | args = parser.parse_args() 511 | 512 | random.seed(args.seed) 513 | 514 | if args.show_seed: 515 | args.out.write('

seed: %d

' % args.seed) 516 | 517 | # Set malloc options 518 | 519 | if args.raw: 520 | Block.header, Block.footer, Block.round, Block.minsz = 0, 0, 1, 0 521 | Block.header, Block.footer, Block.round, Block.minsz = ( 522 | args.header, args.footer, args.round, args.minsz) 523 | 524 | 525 | noerrors = codecs.getreader('utf8')(args.ltrace, errors='ignore') 526 | timeline, boundaries = build_timeline(parse_ltrace(noerrors)) 527 | 528 | gen_html(timeline, boundaries, args.out) 529 | args.out.close() 530 | --------------------------------------------------------------------------------