├── .gitignore ├── LICENSE ├── README.md ├── pwntrace ├── __init__.py ├── heap_ltrace.py └── ltrace.py ├── requirements.txt └── setup.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 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andrea Fioraldi 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 | # pwntrace 2 | Use ltrace with pwnlib.tubes.process instances, useful for heap exploitation 3 | 4 | ## Install 5 | 6 | pwntrace is on PyPI: 7 | 8 | ``` 9 | $ pip install pwntrace 10 | ``` 11 | 12 | I suggest you to use a vitualenv to work with pwntools. 13 | 14 | ## Api 15 | 16 | ltrace: 17 | + `p = ltrace(argv, functions, ...)` create a modified instace of pwnlib.tubes.process for ltrace 18 | + `p.get_trace()` get trace output 19 | + `print_trace(trace)` pretty print p.get_trace or p.trace_now return value 20 | + `p.trace_now()` get_trace + print_trace 21 | 22 | heap_ltrace: 23 | + `p = heap_ltrace(argv, ...)` create a modified instace of pwnlib.tubes.process for ltrace malloc and free 24 | + `p.get_trace()` get trace output 25 | + `print_heap_trace(heap_trace)` 26 | + `p.trace_now()` get_trace + print_trace 27 | + `p.allocd` list of `{"addr": ret_val, "size": arg_val}` objects representing the memory allocated from the last get_trace|trace_now call 28 | + `p.freed` list of addresses (int) representing the memory freed from the last get_trace|trace_now call 29 | + `p.print_allocd()` pretty print allocd 30 | + `p.print_freed()` pretty print freed 31 | 32 | ## Examples 33 | 34 | ```python 35 | >>> from pwntrace import * 36 | >>> p = ltrace("/bin/ls", ["fflush", "fclose"]) 37 | [x] Starting local process '/usr/bin/ltrace' 38 | [+] Starting local process '/usr/bin/ltrace': pid 8737 39 | >>> p.recv() 40 | [*] Process '/usr/bin/ltrace' stopped with exit code 0 (pid 8737) 41 | 'LICENSE pwntrace README.md\n' 42 | >>> p.trace_now() 43 | ls->fflush(0x7efc8f6a0620) = 0 44 | ls->fclose(0x7efc8f6a0620) = 0 45 | ls->fflush(0x7efc8f6a0540) = 0 46 | ls->fclose(0x7efc8f6a0540) = 0 47 | [{'ret': '0', 'fn': 'ls->fflush(0x7efc8f6a0620)'}, {'ret': '0', 'fn': 'ls->fclose(0x7efc8f6a0620)'}, {'ret': '0', 'fn': 'ls->fflush(0x7efc8f6a0540)'}, {'ret': '0', 'fn': 'ls->fclose(0x7efc8f6a0540)'}] 48 | ``` 49 | 50 | ```python 51 | >>> p = heap_ltrace(["/bin/ip", "address"]) 52 | [x] Starting local process '/usr/bin/ltrace' 53 | [+] Starting local process '/usr/bin/ltrace': pid 9694 54 | >>> p.trace_now() 55 | malloc(1276) = 0x12ec010 56 | malloc(64) = 0x12ec520 57 | malloc(1292) = 0x12ec570 58 | malloc(64) = 0x12eca90 59 | malloc(1284) = 0x12ecae0 60 | malloc(64) = 0x12ecff0 61 | malloc(1688) = 0x12ed040 62 | malloc(64) = 0x12ed6e0 63 | malloc(1696) = 0x12ed730 64 | malloc(64) = 0x12edde0 65 | malloc(1576) = 0x12ede30 66 | malloc(64) = 0x12ee460 67 | malloc(84) = 0x12ee4b0 68 | malloc(96) = 0x12ee510 69 | malloc(88) = 0x12ee580 70 | malloc(96) = 0x12ee5e0 71 | malloc(80) = 0x12ee650 72 | malloc(80) = 0x12ee6b0 73 | malloc(80) = 0x12ee710 74 | malloc(80) = 0x12ee770 75 | malloc(24) = 0x12efe20 76 | free(0x12ee4b0) = 77 | free(0x12ee510) = 78 | free(0x12ee580) = 79 | free(0x12ee5e0) = 80 | free(0x12ee650) = 81 | free(0x12ee6b0) = 82 | free(0x12ee710) = 83 | free(0x12ee770) = 84 | free(0x12ec010) = 85 | free(0x12ec570) = 86 | free(0x12ecae0) = 87 | free(0x12ed040) = 88 | free(0x12ed730) = 89 | free(0x12ede30) = 90 | [{'ret': 19841040, 'fn': 'malloc', 'arg': 1276}, {'ret': 19842336, 'fn': 'malloc', 'arg': 64}, {'ret': 19842416, 'fn': 'malloc', 'arg': 1292}, {'ret': 19843728, 'fn': 'malloc', 'arg': 64}, {'ret': 19843808, 'fn': 'malloc', 'arg': 1284}, {'ret': 19845104, 'fn': 'malloc', 'arg': 64}, {'ret': 19845184, 'fn': 'malloc', 'arg': 1688}, {'ret': 19846880, 'fn': 'malloc', 'arg': 64}, {'ret': 19846960, 'fn': 'malloc', 'arg': 1696}, {'ret': 19848672, 'fn': 'malloc', 'arg': 64}, {'ret': 19848752, 'fn': 'malloc', 'arg': 1576}, {'ret': 19850336, 'fn': 'malloc', 'arg': 64}, {'ret': 19850416, 'fn': 'malloc', 'arg': 84}, {'ret': 19850512, 'fn': 'malloc', 'arg': 96}, {'ret': 19850624, 'fn': 'malloc', 'arg': 88}, {'ret': 19850720, 'fn': 'malloc', 'arg': 96}, {'ret': 19850832, 'fn': 'malloc', 'arg': 80}, {'ret': 19850928, 'fn': 'malloc', 'arg': 80}, {'ret': 19851024, 'fn': 'malloc', 'arg': 80}, {'ret': 19851120, 'fn': 'malloc', 'arg': 80}, {'ret': 19856928, 'fn': 'malloc', 'arg': 24}, {'ret': None, 'fn': 'free', 'arg': 19850416}, {'ret': None, 'fn': 'free', 'arg': 19850512}, {'ret': None, 'fn': 'free', 'arg': 19850624}, {'ret': None, 'fn': 'free', 'arg': 19850720}, {'ret': None, 'fn': 'free', 'arg': 19850832}, {'ret': None, 'fn': 'free', 'arg': 19850928}, {'ret': None, 'fn': 'free', 'arg': 19851024}, {'ret': None, 'fn': 'free', 'arg': 19851120}, {'ret': None, 'fn': 'free', 'arg': 19841040}, {'ret': None, 'fn': 'free', 'arg': 19842416}, {'ret': None, 'fn': 'free', 'arg': 19843808}, {'ret': None, 'fn': 'free', 'arg': 19845184}, {'ret': None, 'fn': 'free', 'arg': 19846960}, {'ret': None, 'fn': 'free', 'arg': 19848752}] 91 | >>> p.print_allocd() 92 | >>> ALLOCD <<< 93 | addr: 0x12ec520 size:64 94 | addr: 0x12eca90 size:64 95 | addr: 0x12ecff0 size:64 96 | addr: 0x12ed6e0 size:64 97 | addr: 0x12edde0 size:64 98 | addr: 0x12ee460 size:64 99 | addr: 0x12efe20 size:24 100 | 101 | ``` 102 | 103 | ### Dedication 104 | 105 | In loving memory of malloc_hook 106 | -------------------------------------------------------------------------------- /pwntrace/__init__.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | from ltrace import * 3 | from heap_ltrace import * 4 | -------------------------------------------------------------------------------- /pwntrace/heap_ltrace.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import random 3 | import string 4 | import atexit 5 | import sys 6 | import os 7 | 8 | def print_heap_trace(trace): 9 | if trace == None: 10 | print '\033[0;96m' + " NO TRACE" + '\033[0m' 11 | else: 12 | for e in trace: 13 | if e["fn"] == "malloc": 14 | print '\033[0;32m' + " malloc(" + str(e["arg"]) + ") = " + hex(e["ret"]) + '\033[0m' 15 | elif e["fn"] == "free": 16 | print '\033[0;31m' + " free(" + hex(e["arg"]) + ") = " + '\033[0m' 17 | 18 | def heap_ltrace(argv, env=None, shell=False, **kwargs): 19 | rand_str = "".join([random.choice(string.ascii_letters + string.digits) for n in xrange(16)]) 20 | fifo_name = "/tmp/ltrace-fifo-" + rand_str 21 | os.mkfifo(fifo_name) 22 | 23 | ll = context.log_level 24 | context.log_level = "warning" 25 | t = process(["cat", fifo_name]) 26 | context.log_level = ll 27 | 28 | if shell: 29 | p = process("ltrace -e 'malloc+free' -o '" + fifo_name + "' " + argv, env=env, shell=shell, **kwargs) 30 | else: 31 | if type(argv) == str: 32 | argv = [argv] 33 | p = process(["ltrace", "-e", "malloc+free", "-o", fifo_name] + argv, env=env, shell=shell, **kwargs) 34 | 35 | def get_trace(): 36 | ll = context.log_level 37 | context.log_level = "warning" 38 | try: 39 | r = p.tracer_output.recv() 40 | except EOFError: 41 | context.log_level = ll 42 | return None 43 | context.log_level = ll 44 | 45 | lines = r.split("\n") 46 | trace = [] 47 | for line in lines: 48 | if "=" not in line: 49 | continue 50 | fn, ret = line.split("=") 51 | fn = fn.strip() 52 | ret = ret.strip() 53 | arg = fn[fn.rfind("(") +1: fn.rfind(")")] 54 | 55 | if "malloc" in fn: 56 | fn = "malloc" 57 | try: ret_val = int(ret, 16) 58 | except: ret_val = int(ret, 10) 59 | try: arg_val = int(arg) 60 | except: arg_val = int(arg, 16) 61 | 62 | if ret_val in p.freed: 63 | p.freed.remove(ret_val) 64 | 65 | p.allocd.append({"addr": ret_val, "size": arg_val}) 66 | elif "free" in fn: 67 | fn = "free" 68 | ret_val = None 69 | try: arg_val = int(arg) 70 | except: arg_val = int(arg, 16) 71 | 72 | for d in p.allocd: 73 | if d["addr"] == arg_val: 74 | p.allocd.remove(d) 75 | break 76 | p.freed.append(arg_val) 77 | 78 | trace += [{"fn":fn, "arg":arg_val, "ret":ret_val}] 79 | return trace 80 | 81 | def trace_now(): 82 | trace = p.get_trace() 83 | print_heap_trace(trace) 84 | return trace 85 | 86 | def print_allocd(): 87 | print '\033[0;96m' + " >>> ALLOCD <<<" 88 | for d in p.allocd: 89 | print " addr: 0x%x\tsize:%d" % (d["addr"], d["size"]) 90 | print '\033[0m' 91 | 92 | def print_freed(): 93 | print '\033[0;96m' + " >>> FREED <<<" 94 | for a in p.freed: 95 | print " addr: 0x%x" % a 96 | print '\033[0m' 97 | 98 | 99 | setattr(p, "tracer_output", t) 100 | setattr(p, "get_trace", get_trace) 101 | setattr(p, "trace_now", trace_now) 102 | setattr(p, "allocd", []) 103 | setattr(p, "freed", []) 104 | setattr(p, "print_allocd", print_allocd) 105 | setattr(p, "print_freed", print_freed) 106 | return p 107 | 108 | 109 | -------------------------------------------------------------------------------- /pwntrace/ltrace.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | import random 3 | import string 4 | import atexit 5 | import sys 6 | import os 7 | 8 | def print_trace(trace): 9 | if trace == None: 10 | print '\033[0;96m' + " NO TRACE" + '\033[0m' 11 | else: 12 | for e in trace: 13 | print '\033[0;96m' + " " + e["fn"] + " = " + e["ret"] + '\033[0m' 14 | 15 | def ltrace(argv, functions, env=None, shell=False, **kwargs): 16 | assert type(functions) == list 17 | func_list = "" 18 | for f in functions: 19 | func_list += f + "+" 20 | func_list = func_list[:-1] 21 | 22 | rand_str = "".join([random.choice(string.ascii_letters + string.digits) for n in xrange(16)]) 23 | fifo_name = "/tmp/ltrace-fifo-" + rand_str 24 | os.mkfifo(fifo_name) 25 | 26 | ll = context.log_level 27 | context.log_level = "warning" 28 | t = process(["cat", fifo_name]) 29 | context.log_level = ll 30 | 31 | if shell: 32 | p = process("ltrace -e '" + func_list + "' -o '" + fifo_name + "' " + argv, env=env, shell=shell, **kwargs) 33 | else: 34 | if type(argv) == str: 35 | argv = [argv] 36 | p = process(["ltrace", "-e", func_list, "-o", fifo_name] + argv, env=env, shell=shell, **kwargs) 37 | 38 | def get_trace(): 39 | ll = context.log_level 40 | context.log_level = "warning" 41 | try: 42 | r = p.tracer_output.recv() 43 | except EOFError: 44 | context.log_level = ll 45 | return None 46 | context.log_level = ll 47 | 48 | lines = r.split("\n") 49 | ret = [] 50 | for l in lines: 51 | if "=" not in l: 52 | continue 53 | s = l.split("=") 54 | ret += [{"fn":s[0].strip(), "ret":s[1].strip()}] 55 | return ret 56 | 57 | def trace_now(): 58 | trace = p.get_trace() 59 | print_trace(trace) 60 | return trace 61 | 62 | setattr(p, "tracer_output", t) 63 | setattr(p, "get_trace", get_trace) 64 | setattr(p, "trace_now", trace_now) 65 | return p 66 | 67 | 68 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pwn 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Andrea Fioraldi" 4 | __copyright__ = "Copyright 2017, Andrea Fioraldi" 5 | __license__ = "MIT" 6 | __email__ = "andreafioraldi@gmail.com" 7 | 8 | from setuptools import setup 9 | 10 | VER = "1.0.0" 11 | 12 | setup( 13 | name='pwntrace', 14 | version=VER, 15 | license=__license__, 16 | description='Use ltrace with pwnlib.tubes.process instances', 17 | author=__author__, 18 | author_email=__email__, 19 | url='https://github.com/andreafioraldi/pwntrace', 20 | download_url = 'https://github.com/andreafioraldi/pwntrace/archive/' + VER + '.tar.gz', 21 | package_dir={'pwntrace': 'pwntrace'}, 22 | packages=['pwntrace'], 23 | install_requires=['pwn'] 24 | ) 25 | --------------------------------------------------------------------------------