├── .gitignore ├── LICENSE ├── README.md └── heap tracing ├── blogpost_scripts ├── heap_trace_v1.py ├── pykd_hello_world.py └── soft_hooking.py ├── heap_trace.js ├── heap_trace.py ├── ie_execcommand_uaf.zip ├── overhead testing ├── debugger_runtime.py └── heaptrace_runtime.py ├── sample.html ├── sample.log ├── screenshots ├── pykd_heap_trace_v1.PNG ├── pykd_heap_trace_villoc_example.PNG ├── pykd_hello_world.PNG └── pykd_soft_hooking.PNG └── villoc.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # PyBuilder 56 | target/ 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sam Brown 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # windbg-plugins 2 | Repository for any useful windbg plugins I've written. 3 | 4 | #heap_trace 5 | Hooks heap operations and tracks their arguments and return values. 6 | Run: 7 |
 8 | .load pykd.pyd
 9 | !py "PATH_TO_REPO\heap_trace.py"
10 | 
11 | This will log to your home directory as log.log. You can then create a villoc visualisation of this by running: 12 |
13 | python villoc.py log.log out.html
14 | 
15 | Example villoc output: 16 | ![Example](https://raw.githubusercontent.com/sam-b/windbg-plugins/master/heap%20tracing/screenshots/pykd_heap_trace_villoc_example.PNG) 17 | #Requirements 18 | All plugins use the [pykd](https://pykd.codeplex.com/) python interface for windbg. -------------------------------------------------------------------------------- /heap tracing/blogpost_scripts/heap_trace_v1.py: -------------------------------------------------------------------------------- 1 | import pykd 2 | 3 | return_reg = "rax" 4 | stack_pointer = "rsp" 5 | 6 | def get_address(localAddr): 7 | res = pykd.dbgCommand("x " + localAddr) 8 | result_count = res.count("\n") 9 | if result_count == 0: 10 | print localAddr + " not found." 11 | return None 12 | if result_count > 1: 13 | print "[-] Warning, more than one result for", localAddr 14 | return res.split()[0] 15 | 16 | #RtlAllocateHeap( 17 | # IN PVOID HeapHandle, 18 | # IN ULONG Flags, 19 | # IN ULONG Size ); 20 | class handle_allocate_heap(pykd.eventHandler): 21 | def __init__(self): 22 | addr = get_address("ntdll!RtlAllocateHeap") 23 | if addr == None: 24 | return 25 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 26 | self.bp_end = None 27 | 28 | def enter_call_back(self,bp): 29 | self.out = "RtlAllocateHeap(" 30 | esp = pykd.reg("esp") 31 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 32 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 33 | self.out += hex(pykd.ptrMWord(esp + 0xC)) + ") = " 34 | if self.bp_end == None: 35 | disas = pykd.dbgCommand("uf ntdll!RtlAllocateHeap").split('\n') 36 | for i in disas: 37 | if 'ret' in i: 38 | self.ret_addr = i.split()[0] 39 | break 40 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 41 | return False 42 | 43 | def return_call_back(self,bp): 44 | self.out += hex(pykd.reg(return_reg)) 45 | print self.out 46 | return False 47 | 48 | try: 49 | pykd.reg("rax") 50 | except: 51 | return_reg = "eax" 52 | stack_pointer = "esp" 53 | 54 | handle_allocate_heap() 55 | pykd.go() -------------------------------------------------------------------------------- /heap tracing/blogpost_scripts/pykd_hello_world.py: -------------------------------------------------------------------------------- 1 | import pykd 2 | 3 | print pykd.dbgCommand("!teb") -------------------------------------------------------------------------------- /heap tracing/blogpost_scripts/soft_hooking.py: -------------------------------------------------------------------------------- 1 | import pykd 2 | 3 | def get_address(localAddr): 4 | res = pykd.dbgCommand("x " + localAddr) 5 | result_count = res.count("\n") 6 | if result_count == 0: 7 | print localAddr + " not found." 8 | return None 9 | if result_count > 1: 10 | print "[-] Warning, more than one result for", localAddr 11 | return res.split()[0] 12 | 13 | class handle_allocate_heap(pykd.eventHandler): 14 | def __init__(self): 15 | addr = get_address("ntdll!RtlAllocateHeap") 16 | if addr == None: 17 | return 18 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 19 | self.bp_end = None 20 | 21 | def enter_call_back(self,bp): 22 | print "RtlAllocateHeap called." 23 | if self.bp_end == None: 24 | disas = pykd.dbgCommand("uf ntdll!RtlAllocateHeap").split('\n') 25 | for i in disas: 26 | if 'ret' in i: 27 | self.ret_addr = i.split()[0] 28 | break 29 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 30 | return False 31 | 32 | def return_call_back(self,bp): 33 | print "RtlAllocateHeap returned." 34 | return False 35 | 36 | handle_allocate_heap() 37 | pykd.go() -------------------------------------------------------------------------------- /heap tracing/heap_trace.js: -------------------------------------------------------------------------------- 1 | /* 2 | Uses windbg's JavaScript scripting support to trace user mode memory allocations in a format viewable with villoc 3 | Heavily based off of https://doar-e.github.io/blog/2017/12/01/debugger-data-model/ 4 | */ 5 | 6 | "use strict"; 7 | 8 | var archBits = 0; 9 | var returnReg = null; 10 | var stackPointer = null; 11 | var allocateHeapOut = null; 12 | var reallocateHeapOut = null; 13 | var freeHeapOut = null; 14 | 15 | function hex(val) { 16 | return val.toString(16); 17 | } 18 | 19 | function print(msg) { 20 | host.diagnostics.debugLog(msg); 21 | } 22 | 23 | function findRetAddrs(mod, name){ 24 | var addrs = []; 25 | for(var line of host.namespace.Debugger.Utility.Control.ExecuteCommand("uf " + mod + ':' + name)){ 26 | if(line.includes('ret')){ 27 | var addr = host.parseInt64(line.split(" ")[0], 16); 28 | addrs.push(addr); 29 | } 30 | } 31 | return addrs; 32 | } 33 | /*#RtlAllocateHeap( 34 | # IN PVOID HeapHandle, 35 | # IN ULONG Flags, 36 | # IN ULONG Size );*/ 37 | function handleAllocateHeap() { 38 | var regs = host.currentThread.Registers.User; 39 | var out = "RtlAllocateHeap("; 40 | var args = null; 41 | if(archBits == 32){ 42 | args = host.memory.readMemoryValues(regs[stackPointer] + 4, 3, 4); 43 | } else { 44 | args = [regs.rcx, regs.rdx, regs.r8]; 45 | } 46 | out += hex(args[0]) + ", "; 47 | out += hex(args[1]) + ", "; 48 | out += hex(args[2]) + ") = "; 49 | allocateHeapOut = out; 50 | return false; 51 | } 52 | 53 | /* #RtlReAllocateHeap( 54 | #IN PVOID HeapHandle, 55 | #IN ULONG Flags, 56 | # IN PVOID MemoryPointer, 57 | # IN ULONG Size ); 58 | */ 59 | function handleReAllocateHeap() { 60 | var regs = host.currentThread.Registers.User; 61 | var out = "RtlReAllocateHeap("; 62 | var args = null; 63 | if(archBits == 32){ 64 | args = host.memory.readMemoryValues(regs[stackPointer] + 4, 4, 4); 65 | } else { 66 | args = [regs.rcx, regs.rdx, regs.r8, regs.r9]; 67 | } 68 | out += hex(args[0]) + ", "; 69 | out += hex(args[1]) + ", "; 70 | out += hex(args[2]) + ", "; 71 | out += hex(args[3]) + ") = "; 72 | reallocateHeapOut = out; 73 | return false; 74 | } 75 | 76 | /*#RtlFreeHeap( 77 | #IN PVOID HeapHandle, 78 | #IN ULONG Flags OPTIONAL, 79 | #IN PVOID MemoryPointer );*/ 80 | function handleFreeHeap() { 81 | var regs = host.currentThread.Registers.User; 82 | var out = "RtlFreeHeap("; 83 | var args = null; 84 | if(archBits == 32){ 85 | args = host.memory.readMemoryValues(regs[stackPointer] + 4, 3, 4); 86 | } else { 87 | args = [regs.rcx, regs.rdx, regs.r8]; 88 | } 89 | out += hex(args[0]) + ", "; 90 | out += hex(args[1]) + ", "; 91 | out += hex(args[2]) + ") = "; 92 | freeHeapOut = out; 93 | return false; 94 | } 95 | 96 | function handleAllocateHeapRet() { 97 | var regs = host.currentThread.Registers.User; 98 | if(allocateHeapOut != null){ 99 | print(allocateHeapOut + hex(regs[returnReg]) + "\r\n"); 100 | allocateHeapOut = null; 101 | } 102 | return false; 103 | } 104 | 105 | function handleReAllocateHeapRet() { 106 | var regs = host.currentThread.Registers.User; 107 | if(reallocateHeapOut != null){ 108 | print(reallocateHeapOut + hex(regs[returnReg]) + "\r\n"); 109 | reallocateHeapOut = null; 110 | } 111 | return false; 112 | } 113 | 114 | function handleFreeHeapRet() { 115 | var regs = host.currentThread.Registers.User; 116 | if(freeHeapOut != null){ 117 | print(freeHeapOut + hex(regs[returnReg]) + "\r\n"); 118 | freeHeapOut = null; 119 | } 120 | return false; 121 | } 122 | 123 | function invokeScript() { 124 | try { 125 | host.currentThread.Registers.User.rax; 126 | archBits = 64; 127 | returnReg = "rax"; 128 | stackPointer = "rsp"; 129 | } catch (e) { 130 | archBits = 32; 131 | returnRed = "eax"; 132 | stackPointer = "esp"; 133 | } 134 | 135 | print("Running on a " + archBits + "-bit process.\r\n"); 136 | 137 | var RtlAllocateHeap = host.getModuleSymbolAddress('ntdll', 'RtlAllocateHeap'); 138 | var RtlFreeHeap = host.getModuleSymbolAddress('ntdll', 'RtlFreeHeap'); 139 | var RtlReAllocateHeap = host.getModuleSymbolAddress('ntdll', 'RtlReAllocateHeap'); 140 | 141 | print('Hooking:\r\n\tRltAllocateHeap: ' + hex(RtlAllocateHeap) + "\r\n\tRtlFreeHeap: " + hex(RtlFreeHeap) + "\r\n\tRtlReAllocateHeap: " + hex(RtlReAllocateHeap) + "\r\n"); 142 | var breakpointsAlreadySet = host.currentProcess.Debug.Breakpoints.Any( 143 | bp => bp.Address == RtlAllocateHeap || bp.Address == RtlFreeHeap || by.Address == RtlReAllocateHeap 144 | ); 145 | if(breakpointsAlreadySet == false) { 146 | host.namespace.Debugger.Utility.Control.ExecuteCommand('bp /w "@$scriptContents.handleAllocateHeap()" ' + RtlAllocateHeap.toString(16)); 147 | host.namespace.Debugger.Utility.Control.ExecuteCommand('bp /w "@$scriptContents.handleFreeHeap()" ' + RtlFreeHeap.toString(16)); 148 | host.namespace.Debugger.Utility.Control.ExecuteCommand('bp /w "@$scriptContents.handleReAllocateHeap()" ' + RtlReAllocateHeap.toString(16)); 149 | } 150 | var RtlAllocateHeapRet = findRetAddrs("ntdll", "RtlAllocateHeap"); 151 | var RtlFreeHeapRet = findRetAddrs('ntdll', 'RtlFreeHeap'); 152 | var RtlReAllocateHeapRet = findRetAddrs('ntdll', 'RtlReAllocateHeap'); 153 | 154 | print('\tRltAllocateHeapRet: ' + hex(RtlAllocateHeapRet) + "\r\n\tRtlFreeHeapRet: " + hex(RtlFreeHeapRet) + "\r\n\tRtlReAllocateHeapRet: " + hex(RtlReAllocateHeapRet) + "\r\n"); 155 | 156 | var retBreakpointsAlreadySet = host.currentProcess.Debug.Breakpoints.Any( 157 | bp => bp.Address in RtlAllocateHeapRet || bp.Address in RtlFreeHeapRet || bp.Address in RtlReAllocateHeapRet 158 | ); 159 | 160 | if(retBreakpointsAlreadySet == false) { 161 | for(var addr in RtlAllocateHeapRet){ 162 | host.namespace.Debugger.Utility.Control.ExecuteCommand('bp /w "@$scriptContents.handleAllocateHeapRet()" ' + RtlAllocateHeapRet[addr].toString(16)); 163 | } 164 | for(var addr in RtlFreeHeapRet){ 165 | host.namespace.Debugger.Utility.Control.ExecuteCommand('bp /w "@$scriptContents.handleFreeHeapRet()" ' + RtlFreeHeapRet[addr].toString(16)); 166 | } 167 | for(var addr in RtlReAllocateHeapRet){ 168 | host.namespace.Debugger.Utility.Control.ExecuteCommand('bp /w "@$scriptContents.handleReAllocateHeapRet()" ' + RtlReAllocateHeapRet[addr].toString(16)); 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /heap tracing/heap_trace.py: -------------------------------------------------------------------------------- 1 | import pykd 2 | from os.path import expanduser 3 | 4 | home = expanduser("~") 5 | return_reg = "rax" 6 | stack_pointer = "rsp" 7 | arch_bits = 64 8 | log = None 9 | 10 | def get_address(localAddr): 11 | res = pykd.dbgCommand("x " + localAddr) 12 | result_count = res.count("\n") 13 | if result_count == 0: 14 | print localAddr + " not found." 15 | return None 16 | if result_count > 1: 17 | print "[-] Warning, more than one result for", localAddr 18 | return res.split()[0] 19 | 20 | #RtlAllocateHeap( 21 | # IN PVOID HeapHandle, 22 | # IN ULONG Flags, 23 | # IN ULONG Size ); 24 | class handle_allocate_heap(pykd.eventHandler): 25 | def __init__(self): 26 | addr = get_address("ntdll!RtlAllocateHeap") 27 | if addr == None: 28 | return 29 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 30 | self.bp_end = None 31 | 32 | def enter_call_back(self,bp): 33 | self.out = "RtlAllocateHeap(" 34 | if arch_bits == 32: 35 | esp = pykd.reg(stack_pointer) 36 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 37 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 38 | self.out += hex(pykd.ptrMWord(esp + 0xC)) + ") = " 39 | else: 40 | self.out += hex(pykd.reg("rcx")) + " , " 41 | self.out += hex(pykd.reg("rdx")) + " , " 42 | self.out += hex(pykd.reg("r8")) + ") = " 43 | if self.bp_end == None: 44 | disas = pykd.dbgCommand("uf ntdll!RtlAllocateHeap").split('\n') 45 | for i in disas: 46 | if 'ret' in i: 47 | self.ret_addr = i.split()[0] 48 | break 49 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 50 | return False 51 | 52 | def return_call_back(self,bp): 53 | log.write(self.out + hex(pykd.reg(return_reg)) + "\n") 54 | return False 55 | 56 | #RtlFreeHeap( 57 | #IN PVOID HeapHandle, 58 | #IN ULONG Flags OPTIONAL, 59 | #IN PVOID MemoryPointer ); 60 | class handle_free_heap(pykd.eventHandler): 61 | def __init__(self): 62 | addr = get_address("ntdll!RtlFreeHeap") 63 | if addr == None: 64 | return 65 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 66 | self.bp_end = None 67 | 68 | def enter_call_back(self,bp): 69 | self.out = "RtlFreeHeap(" 70 | if arch_bits == 32: 71 | esp = pykd.reg(stack_pointer) 72 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 73 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 74 | self.out += hex(pykd.ptrPtr(esp + 0xC)) + ") = " 75 | else: 76 | self.out += hex(pykd.reg("rcx")) + " , " 77 | self.out += hex(pykd.reg("rdx")) + " , " 78 | self.out += hex(pykd.reg("r8")) + ") = " 79 | if self.bp_end == None: 80 | disas = pykd.dbgCommand("uf ntdll!RtlFreeHeap").split('\n') 81 | for i in disas: 82 | if 'ret' in i: 83 | self.ret_addr = i.split()[0] 84 | break 85 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 86 | return False 87 | 88 | def return_call_back(self,bp): 89 | #returns a BOOLEAN which is a byte under the hood 90 | ret_val = hex(pykd.reg("al")) 91 | log.write(self.out + ret_val + "\n") 92 | return False 93 | 94 | #RtlReAllocateHeap( 95 | #IN PVOID HeapHandle, 96 | #IN ULONG Flags, 97 | # IN PVOID MemoryPointer, 98 | # IN ULONG Size ); 99 | 100 | class handle_realloc_heap(pykd.eventHandler): 101 | def __init__(self): 102 | addr = get_address("ntdll!RtlReAllocateHeap") 103 | if addr == None: 104 | return 105 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 106 | self.bp_end = None 107 | 108 | def enter_call_back(self,bp): 109 | self.out = "RtlReAllocateHeap(" 110 | if arch_bits == 32: 111 | esp = pykd.reg(stack_pointer) 112 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 113 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 114 | self.out += hex(pykd.ptrPtr(esp + 0xC)) + " , " 115 | self.out += hex(pykd.ptrMWord(esp + 0x10)) + ") = " 116 | else: 117 | self.out += hex(pykd.reg("rcx")) + " , " 118 | self.out += hex(pykd.reg("rdx")) + " , " 119 | self.out += hex(pykd.reg("r8")) + " , " 120 | self.out += hex(pykd.reg("r9")) + ") = " 121 | if self.bp_end == None: 122 | disas = pykd.dbgCommand("uf ntdll!RtlReAllocateHeap").split('\n') 123 | for i in disas: 124 | if 'ret' in i: 125 | self.ret_addr = i.split()[0] 126 | break 127 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 128 | return False 129 | 130 | def return_call_back(self,bp): 131 | log.write(self.out + hex(pykd.reg(return_reg)) + "\n") 132 | return False 133 | 134 | log = open(home + "\log.log","w+") 135 | 136 | try: 137 | pykd.reg("rax") 138 | except: 139 | arch_bits = 32 140 | return_reg = "eax" 141 | stack_pointer = "esp" 142 | 143 | handle_allocate_heap() 144 | handle_free_heap() 145 | handle_realloc_heap() 146 | pykd.go() -------------------------------------------------------------------------------- /heap tracing/ie_execcommand_uaf.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/windbg-plugins/00b6d5a2b7dc4b87d287015a7780d962e40e6a6f/heap tracing/ie_execcommand_uaf.zip -------------------------------------------------------------------------------- /heap tracing/overhead testing/debugger_runtime.py: -------------------------------------------------------------------------------- 1 | import pykd 2 | from os.path import expanduser 3 | import timeit 4 | 5 | start = timeit.default_timer() 6 | 7 | home = expanduser("~") 8 | return_reg = "rax" 9 | stack_pointer = "rsp" 10 | arch_bits = 64 11 | log = None 12 | def get_address(localAddr): 13 | res = pykd.dbgCommand("x " + localAddr) 14 | result_count = res.count("\n") 15 | if result_count == 0: 16 | print localAddr + " not found." 17 | return None 18 | if result_count > 1: 19 | print "[-] Warning, more than one result for", localAddr 20 | return res.split()[0] 21 | 22 | class time(pykd.eventHandler): 23 | def __init__(self): 24 | addr = get_address("jscript9!StrToDbl") 25 | if addr == None: 26 | return 27 | self.start = timeit.default_timer() 28 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 29 | def enter_call_back(self,bp): 30 | end = timeit.default_timer() 31 | print "Heap spray took: " + str(end - self.start) 32 | return True 33 | 34 | time() 35 | pykd.go() -------------------------------------------------------------------------------- /heap tracing/overhead testing/heaptrace_runtime.py: -------------------------------------------------------------------------------- 1 | import pykd 2 | from os.path import expanduser 3 | import timeit 4 | 5 | home = expanduser("~") 6 | return_reg = "rax" 7 | stack_pointer = "rsp" 8 | arch_bits = 64 9 | log = None 10 | 11 | def get_address(localAddr): 12 | res = pykd.dbgCommand("x " + localAddr) 13 | result_count = res.count("\n") 14 | if result_count == 0: 15 | print localAddr + " not found." 16 | return None 17 | if result_count > 1: 18 | print "[-] Warning, more than one result for", localAddr 19 | return res.split()[0] 20 | 21 | class start(pykd.eventHandler): 22 | def __init__(self): 23 | addr = get_address("jscript9!StrToDbl") 24 | if addr == None: 25 | return 26 | self.start = timeit.default_timer() 27 | handle_allocate_heap() 28 | handle_free_heap() 29 | handle_realloc_heap() 30 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 31 | 32 | def enter_call_back(self,bp): 33 | end = timeit.default_timer() 34 | print "Heap spray took: " + str(end - self.start) 35 | return True 36 | 37 | 38 | #RtlAllocateHeap( 39 | # IN PVOID HeapHandle, 40 | # IN ULONG Flags, 41 | # IN ULONG Size ); 42 | class handle_allocate_heap(pykd.eventHandler): 43 | def __init__(self): 44 | addr = get_address("ntdll!RtlAllocateHeap") 45 | if addr == None: 46 | return 47 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 48 | self.bp_end = None 49 | 50 | def enter_call_back(self,bp): 51 | self.out = "RtlAllocateHeap(" 52 | if arch_bits == 32: 53 | esp = pykd.reg(stack_pointer) 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.bp_end == None: 62 | disas = pykd.dbgCommand("uf ntdll!RtlAllocateHeap").split('\n') 63 | for i in disas: 64 | if 'ret' in i: 65 | self.ret_addr = 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,bp): 71 | log.write(self.out + hex(pykd.reg(return_reg)) + "\n") 72 | return False 73 | 74 | #RtlFreeHeap( 75 | #IN PVOID HeapHandle, 76 | #IN ULONG Flags OPTIONAL, 77 | #IN PVOID MemoryPointer ); 78 | class handle_free_heap(pykd.eventHandler): 79 | def __init__(self): 80 | addr = get_address("ntdll!RtlFreeHeap") 81 | if addr == None: 82 | return 83 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 84 | self.bp_end = None 85 | 86 | def enter_call_back(self,bp): 87 | self.out = "RtlFreeHeap(" 88 | if arch_bits == 32: 89 | esp = pykd.reg(stack_pointer) 90 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 91 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 92 | self.out += hex(pykd.ptrPtr(esp + 0xC)) + ") = " 93 | else: 94 | self.out += hex(pykd.reg("rcx")) + " , " 95 | self.out += hex(pykd.reg("rdx")) + " , " 96 | self.out += hex(pykd.reg("r8")) + ") = " 97 | if self.bp_end == None: 98 | disas = pykd.dbgCommand("uf ntdll!RtlFreeHeap").split('\n') 99 | for i in disas: 100 | if 'ret' in i: 101 | self.ret_addr = i.split()[0] 102 | break 103 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 104 | return False 105 | 106 | def return_call_back(self,bp): 107 | #returns a BOOLEAN which is a byte under the hood 108 | ret_val = hex(pykd.reg("al")) 109 | log.write(self.out + ret_val + "\n") 110 | return False 111 | 112 | #RtlReAllocateHeap( 113 | #IN PVOID HeapHandle, 114 | #IN ULONG Flags, 115 | # IN PVOID MemoryPointer, 116 | # IN ULONG Size ); 117 | 118 | class handle_realloc_heap(pykd.eventHandler): 119 | def __init__(self): 120 | addr = get_address("ntdll!RtlReAllocateHeap") 121 | if addr == None: 122 | return 123 | self.bp_init = pykd.setBp(int(addr, 16), self.enter_call_back) 124 | self.bp_end = None 125 | 126 | def enter_call_back(self,bp): 127 | self.out = "RtlReAllocateHeap(" 128 | if arch_bits == 32: 129 | esp = pykd.reg(stack_pointer) 130 | self.out += hex(pykd.ptrPtr(esp + 4)) + " , " 131 | self.out += hex(pykd.ptrMWord(esp + 0x8)) + " , " 132 | self.out += hex(pykd.ptrPtr(esp + 0xC)) + " , " 133 | self.out += hex(pykd.ptrMWord(esp + 0x10)) + ") = " 134 | else: 135 | self.out += hex(pykd.reg("rcx")) + " , " 136 | self.out += hex(pykd.reg("rdx")) + " , " 137 | self.out += hex(pykd.reg("r8")) + " , " 138 | self.out += hex(pykd.reg("r9")) + ") = " 139 | if self.bp_end == None: 140 | disas = pykd.dbgCommand("uf ntdll!RtlReAllocateHeap").split('\n') 141 | for i in disas: 142 | if 'ret' in i: 143 | self.ret_addr = i.split()[0] 144 | break 145 | self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.return_call_back) 146 | return False 147 | 148 | def return_call_back(self,bp): 149 | log.write(self.out + hex(pykd.reg(return_reg)) + "\n") 150 | return False 151 | 152 | log = open(home + "\log.log","w+") 153 | 154 | try: 155 | pykd.reg("rax") 156 | except: 157 | arch_bits = 32 158 | return_reg = "eax" 159 | stack_pointer = "esp" 160 | 161 | start() 162 | pykd.go() -------------------------------------------------------------------------------- /heap tracing/sample.html: -------------------------------------------------------------------------------- 1 | 54 | 66 | 67 |
68 |
69 |
70 |
0xb33eb0
unknown
71 |
72 |

RtlFreeHeap(0xb20000, 0x0, 0xb33eb8) = 0x1

Couldn't find block at 0xb33eb0, added marker.

73 |
74 |
75 |
76 |
0xb33eb0
+ 0x19946b8
77 |
0x24c8568
+ 0x50 (0x3c)
78 |
79 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

80 |
81 |
82 |
83 |
0xb33eb0
+ 0x19946b8
84 |
0x24c8568
+ 0x50 (0x3c)
85 |
0x24c85b8
+ 0x3f18
86 |
0x24cc4d0
unknown
87 |
88 |

RtlFreeHeap(0x230000, 0x0, 0x24cc4d8) = 0x1

Couldn't find block at 0x24cc4d0, added marker.

89 |
90 |
91 |
92 |
0xb33eb0
+ 0x19946b8
93 |
0x24c8568
+ 0x50 (0x3c)
94 |
0x24c85b8
+ 0x3ee0
95 |
0x24cc498
unknown
96 |
97 |

RtlFreeHeap(0x230000, 0x0, 0x24cc4a0) = 0x1

Couldn't find block at 0x24cc498, added marker.

98 |
99 |
100 |
101 |
0xb33eb0
+ 0x1940618
102 |
0x24744c8
unknown
103 |
0x24744e8
+ 0x54080
104 |
0x24c8568
+ 0x50 (0x3c)
105 |
106 |

RtlFreeHeap(0x230000, 0x0, 0x24744d0) = 0x1

Couldn't find block at 0x24744c8, added marker.

107 |
108 |
109 |
110 |
0xb33eb0
+ 0x153e70
111 |
0xc87d20
unknown
112 |
0xc87d40
+ 0x1840828
113 |
0x24c8568
+ 0x50 (0x3c)
114 |
115 |

RtlFreeHeap(0x230000, 0x0, 0xc87d28) = 0x1

Couldn't find block at 0xc87d20, added marker.

116 |
117 |
118 |
119 |
0xb33eb0
+ 0x153e70
120 |
0xc87d20
+ 0x60 (0x54)
121 |
0xc87d80
+ 0x18407e8
122 |
0x24c8568
+ 0x50 (0x3c)
123 |
124 |

RtlAllocateHeap(0x230000, 0x0, 0x54) = 0xc87d28

125 |
126 |
127 |
128 |
0xb33eb0
+ 0x153e70
129 |
0xc87d20
+ 0x60 (0x54)
130 |
0xc87d80
+ 0x17e0f70
131 |
0x2468cf0
+ 0x20 (0x11)
132 |
0x2468d10
+ 0x5f858
133 |
0x24c8568
+ 0x50 (0x3c)
134 |
135 |

RtlAllocateHeap(0x230000, 0x0, 0x11) = 0x2468cf8

136 |
137 |
138 |
139 |
0xb33eb0
+ 0x153e70
140 |
0xc87d20
+ 0x60 (0x54)
141 |
0xc87d80
+ 0x17e0ed0
142 |
0x2468c50
+ 0x20 (0x11)
143 |
0x2468c70
+ 0x80
144 |
0x2468cf0
+ 0x20 (0x11)
145 |
0x2468d10
+ 0x5f858
146 |
0x24c8568
+ 0x50 (0x3c)
147 |
148 |

RtlAllocateHeap(0x230000, 0x0, 0x11) = 0x2468c58

149 |
150 |
151 |
152 |
0xb33eb0
+ 0x153e70
153 |
0xc87d20
+ 0x60 (0x54)
154 |
0xc87d80
+ 0x17e0ed0
155 |
0x2468c50
+ 0x20 (0x11)
156 |
0x2468c70
+ 0x80
157 |
0x2468cf0
+ 0x20 (0x11)
158 |
0x2468d10
+ 0x5f858
159 |
0x24c8568
+ 0x50 (0x3c)
160 |
0x24c85b8
+ 0x10f28
161 |
0x24d94e0
+ 0x50 (0x48)
162 |
163 |

RtlAllocateHeap(0x230000, 0x0, 0x48) = 0x24d94e8

164 |
165 |
166 |
167 |
0xb33eb0
+ 0x153e70
168 |
0xc87d20
+ 0x60 (0x54)
169 |
0xc87d80
+ 0x17e0ed0
170 |
0x2468c50
+ 0x20 (0x11)
171 |
0x2468c70
+ 0x80
172 |
0x2468cf0
+ 0x20 (0x11)
173 |
0x2468d10
+ 0x107a8
174 |
0x24794b8
+ 0x20 (0x4)
175 |
0x24794d8
+ 0x4f090
176 |
0x24c8568
+ 0x50 (0x3c)
177 |
0x24c85b8
+ 0x10f28
178 |
0x24d94e0
+ 0x50 (0x48)
179 |
180 |

RtlAllocateHeap(0x230000, 0x0, 0x4) = 0x24794c0

181 |
182 |
183 |
184 |
0xb33eb0
+ 0x153e70
185 |
0xc87d20
+ 0x60 (0x54)
186 |
0xc87d80
+ 0x17e0ed0
187 |
0x2468c50
+ 0x20 (0x11)
188 |
0x2468c70
+ 0x80
189 |
0x2468cf0
+ 0x20 (0x11)
190 |
0x2468d10
+ 0x5f858
191 |
0x24c8568
+ 0x50 (0x3c)
192 |
0x24c85b8
+ 0x10f28
193 |
0x24d94e0
+ 0x50 (0x48)
194 |
195 |

RtlFreeHeap(0x230000, 0x0, 0x24794c0) = 0x1

196 |
197 |
198 |
199 |
0xb33eb0
+ 0x153e70
200 |
0xc87d20
+ 0x60 (0x54)
201 |
0xc87d80
+ 0x17e0ed0
202 |
0x2468c50
+ 0x20 (0x11)
203 |
0x2468c70
+ 0x80
204 |
0x2468cf0
+ 0x20 (0x11)
205 |
0x2468d10
+ 0x707d0
206 |
0x24d94e0
+ 0x50 (0x48)
207 |
208 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

209 |
210 |
211 |
212 |
0xb33eb0
+ 0x153e70
213 |
0xc87d20
+ 0x60 (0x54)
214 |
0xc87d80
+ 0x17e0ed0
215 |
0x2468c50
+ 0x20 (0x11)
216 |
0x2468c70
+ 0x80
217 |
0x2468cf0
+ 0x20 (0x11)
218 |
0x2468d10
+ 0x5f858
219 |
0x24c8568
+ 0x50 (0x3c)
220 |
0x24c85b8
+ 0x10f28
221 |
0x24d94e0
+ 0x50 (0x48)
222 |
223 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

224 |
225 |
226 |
227 |
0xb33eb0
+ 0x153e70
228 |
0xc87d20
+ 0x60 (0x54)
229 |
0xc87d80
+ 0x17e0ed0
230 |
0x2468c50
+ 0x20 (0x11)
231 |
0x2468c70
+ 0x80
232 |
0x2468cf0
+ 0x20 (0x11)
233 |
0x2468d10
+ 0x707d0
234 |
0x24d94e0
+ 0x50 (0x48)
235 |
236 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

237 |
238 |
239 |
240 |
0xb33eb0
+ 0x153e70
241 |
0xc87d20
+ 0x60 (0x54)
242 |
0xc87d80
+ 0x17e0ed0
243 |
0x2468c50
+ 0x20 (0x11)
244 |
0x2468c70
+ 0x80
245 |
0x2468cf0
+ 0x20 (0x11)
246 |
0x2468d10
+ 0x5f858
247 |
0x24c8568
+ 0x50 (0x3c)
248 |
0x24c85b8
+ 0x10f28
249 |
0x24d94e0
+ 0x50 (0x48)
250 |
251 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

252 |
253 |
254 |
255 |
0xb33eb0
+ 0x153e70
256 |
0xc87d20
+ 0x60 (0x54)
257 |
0xc87d80
+ 0x17e0ed0
258 |
0x2468c50
+ 0x20 (0x11)
259 |
0x2468c70
+ 0x80
260 |
0x2468cf0
+ 0x20 (0x11)
261 |
0x2468d10
+ 0x707d0
262 |
0x24d94e0
+ 0x50 (0x48)
263 |
264 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

265 |
266 |
267 |
268 |
0xb33eb0
+ 0x153e70
269 |
0xc87d20
+ 0x60 (0x54)
270 |
0xc87d80
+ 0x17e0ed0
271 |
0x2468c50
+ 0x20 (0x11)
272 |
0x2468c70
+ 0x80
273 |
0x2468cf0
+ 0x20 (0x11)
274 |
0x2468d10
+ 0x5f858
275 |
0x24c8568
+ 0x50 (0x3c)
276 |
0x24c85b8
+ 0x10f28
277 |
0x24d94e0
+ 0x50 (0x48)
278 |
279 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

280 |
281 |
282 |
283 |
0xb33eb0
+ 0x153e70
284 |
0xc87d20
+ 0x60 (0x54)
285 |
0xc87d80
+ 0x17e0ed0
286 |
0x2468c50
+ 0x20 (0x11)
287 |
0x2468c70
+ 0x80
288 |
0x2468cf0
+ 0x20 (0x11)
289 |
0x2468d10
+ 0x707d0
290 |
0x24d94e0
+ 0x50 (0x48)
291 |
292 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

293 |
294 |
295 |
296 |
0xb33eb0
+ 0x153e70
297 |
0xc87d20
+ 0x60 (0x54)
298 |
0xc87d80
+ 0x17e0ed0
299 |
0x2468c50
+ 0x20 (0x11)
300 |
0x2468c70
+ 0x80
301 |
0x2468cf0
+ 0x20 (0x11)
302 |
0x2468d10
+ 0x5f858
303 |
0x24c8568
+ 0x50 (0x3c)
304 |
0x24c85b8
+ 0x10f28
305 |
0x24d94e0
+ 0x50 (0x48)
306 |
307 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

308 |
309 |
310 |
311 |
0xb33eb0
+ 0x153e70
312 |
0xc87d20
+ 0x60 (0x54)
313 |
0xc87d80
+ 0x17e0ed0
314 |
0x2468c50
+ 0x20 (0x11)
315 |
0x2468c70
+ 0x80
316 |
0x2468cf0
+ 0x20 (0x11)
317 |
0x2468d10
+ 0x707d0
318 |
0x24d94e0
+ 0x50 (0x48)
319 |
320 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

321 |
322 |
323 |
324 |
0xb33eb0
+ 0x153e70
325 |
0xc87d20
+ 0x60 (0x54)
326 |
0xc87d80
+ 0x17e0ed0
327 |
0x2468c50
+ 0x20 (0x11)
328 |
0x2468c70
+ 0x80
329 |
0x2468cf0
+ 0x20 (0x11)
330 |
0x2468d10
+ 0x5f858
331 |
0x24c8568
+ 0x50 (0x3c)
332 |
0x24c85b8
+ 0x10f28
333 |
0x24d94e0
+ 0x50 (0x48)
334 |
335 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

336 |
337 |
338 |
339 |
0xb33eb0
+ 0x153e70
340 |
0xc87d20
+ 0x60 (0x54)
341 |
0xc87d80
+ 0x17e0ed0
342 |
0x2468c50
+ 0x20 (0x11)
343 |
0x2468c70
+ 0x80
344 |
0x2468cf0
+ 0x20 (0x11)
345 |
0x2468d10
+ 0x707d0
346 |
0x24d94e0
+ 0x50 (0x48)
347 |
348 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

349 |
350 |
351 |
352 |
0xb33eb0
+ 0x153e70
353 |
0xc87d20
+ 0x60 (0x54)
354 |
0xc87d80
+ 0x17e0ed0
355 |
0x2468c50
+ 0x20 (0x11)
356 |
0x2468c70
+ 0x80
357 |
0x2468cf0
+ 0x20 (0x11)
358 |
0x2468d10
+ 0x5f858
359 |
0x24c8568
+ 0x50 (0x3c)
360 |
0x24c85b8
+ 0x10f28
361 |
0x24d94e0
+ 0x50 (0x48)
362 |
363 |

RtlAllocateHeap(0x230000, 0x0, 0x3c) = 0x24c8570

364 |
365 |
366 |
367 |
0xb33eb0
+ 0x153e70
368 |
0xc87d20
+ 0x60 (0x54)
369 |
0xc87d80
+ 0x17e0ed0
370 |
0x2468c50
+ 0x20 (0x11)
371 |
0x2468c70
+ 0x80
372 |
0x2468cf0
+ 0x20 (0x11)
373 |
0x2468d10
+ 0x707d0
374 |
0x24d94e0
+ 0x50 (0x48)
375 |
376 |

RtlFreeHeap(0x230000, 0x0, 0x24c8570) = 0x1

377 |
378 |
379 | 380 | -------------------------------------------------------------------------------- /heap tracing/sample.log: -------------------------------------------------------------------------------- 1 | RtlFreeHeap(0xb20000L , 0x0L , 0xb33eb8L) = 0x1 2 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 3 | RtlFreeHeap(0x230000L , 0x0L , 0x24cc4d8L) = 0x1 4 | RtlFreeHeap(0x230000L , 0x0L , 0x24cc4a0L) = 0x1 5 | RtlFreeHeap(0x230000L , 0x0L , 0x24744d0L) = 0x1 6 | RtlFreeHeap(0x230000L , 0x0L , 0xc87d28L) = 0x1 7 | RtlAllocateHeap(0x230000L , 0x0L , 0x54L) = 0xc87d28 8 | RtlAllocateHeap(0x230000L , 0x0L , 0x11L) = 0x2468cf8 9 | RtlAllocateHeap(0x230000L , 0x0L , 0x11L) = 0x2468c58 10 | RtlAllocateHeap(0x230000L , 0x0L , 0x48L) = 0x24d94e8 11 | RtlAllocateHeap(0x230000L , 0x0L , 0x4L) = 0x24794c0 12 | RtlFreeHeap(0x230000L , 0x0L , 0x24794c0L) = 0x1 13 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 14 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 15 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 16 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 17 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 18 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 19 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 20 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 21 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 22 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 23 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 24 | RtlAllocateHeap(0x230000L , 0x0L , 0x3cL) = 0x24c8570 25 | RtlFreeHeap(0x230000L , 0x0L , 0x24c8570L) = 0x1 26 | -------------------------------------------------------------------------------- /heap tracing/screenshots/pykd_heap_trace_v1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/windbg-plugins/00b6d5a2b7dc4b87d287015a7780d962e40e6a6f/heap tracing/screenshots/pykd_heap_trace_v1.PNG -------------------------------------------------------------------------------- /heap tracing/screenshots/pykd_heap_trace_villoc_example.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/windbg-plugins/00b6d5a2b7dc4b87d287015a7780d962e40e6a6f/heap tracing/screenshots/pykd_heap_trace_villoc_example.PNG -------------------------------------------------------------------------------- /heap tracing/screenshots/pykd_hello_world.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/windbg-plugins/00b6d5a2b7dc4b87d287015a7780d962e40e6a6f/heap tracing/screenshots/pykd_hello_world.PNG -------------------------------------------------------------------------------- /heap tracing/screenshots/pykd_soft_hooking.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sam-b/windbg-plugins/00b6d5a2b7dc4b87d287015a7780d962e40e6a6f/heap tracing/screenshots/pykd_soft_hooking.PNG -------------------------------------------------------------------------------- /heap tracing/villoc.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"^([a-zA-Z_]+)\((.*)\) += (.*)") 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 | continue 237 | 238 | try: 239 | func, args, ret = match_call.findall(line)[0] 240 | except Exception: 241 | 242 | try: 243 | # maybe this stopped the program 244 | func, args = match_err.findall(line)[0] 245 | ret = None 246 | except Exception: 247 | print("ignoring line: %s" % line) 248 | continue 249 | 250 | args = map(sanitize, args.split(", ")) 251 | ret = sanitize(ret) 252 | 253 | yield func, args, ret 254 | 255 | 256 | def build_timeline(events): 257 | 258 | boundaries = set() 259 | timeline = [State()] 260 | 261 | for func, args, ret in events: 262 | 263 | try: 264 | op = operations[func] 265 | except KeyError: 266 | continue 267 | 268 | state = State(filter(lambda x: not x.tmp, timeline[-1])) 269 | 270 | call = "%s(%s)" % (func, ", ".join("%#x" % a for a in args)) 271 | 272 | if ret is None: 273 | state.errors.append("%s = " % call) 274 | else: 275 | state.info.append("%s = %#x" % (call, ret)) 276 | 277 | op(state, ret, *args) 278 | boundaries.update(state.boundaries()) 279 | timeline.append(state) 280 | 281 | return timeline, boundaries 282 | 283 | 284 | def random_color(r=200, g=200, b=125): 285 | 286 | red = (random.randrange(0, 256) + r) / 2 287 | green = (random.randrange(0, 256) + g) / 2 288 | blue = (random.randrange(0, 256) + b) / 2 289 | 290 | return (red, green, blue) 291 | 292 | 293 | def print_state(out, boundaries, state): 294 | 295 | out.write('
\n' % ("error" if state.errors else "")) 296 | 297 | known_stops = set() 298 | 299 | todo = {x.start():x for x in state} 300 | while todo: 301 | 302 | out.write('
\n') 303 | 304 | done = set() 305 | 306 | current = None 307 | last = 0 308 | 309 | for i, b in enumerate(boundaries): 310 | 311 | # If this block has size 0; make it continue until the 312 | # next boundary anyway. The size will be displayed as 313 | # 0 or unknown anyway and it shouldn't be too confusing. 314 | if current and current.end() != b and current.start() != current.end(): 315 | continue 316 | 317 | if current: # stops here. 318 | known_stops.add(i) 319 | current.gen_html(out, i - last) 320 | done.add(current) 321 | last = i 322 | 323 | current = None 324 | try: 325 | current = todo[b] 326 | except: 327 | continue 328 | 329 | if last != i: 330 | 331 | # We want to show from previous known_stop. 332 | 333 | for s in reversed(range(last, i+1)): 334 | if s in known_stops: 335 | break 336 | 337 | if s != last: 338 | Empty(boundaries[last], boundaries[s], 339 | display=False).gen_html(out, s - last) 340 | known_stops.add(s) 341 | 342 | if s != i: 343 | Empty(boundaries[s], b).gen_html(out, i - s) 344 | known_stops.add(i) 345 | 346 | last = i 347 | 348 | 349 | if current: 350 | raise RuntimeError("Block was started but never finished.") 351 | 352 | if not done: 353 | raise RuntimeError("Some block(s) don't match boundaries.") 354 | 355 | out.write('
\n') 356 | 357 | todo = {x.start():x for x in todo.values() if x not in done} 358 | 359 | out.write('
') 360 | 361 | for msg in state.info: 362 | out.write('

%s

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

%s

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

seed: %d

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