├── requirements.txt ├── examples ├── crackme │ ├── crackmenoptrace │ └── ReadMe.md ├── ais3crackme │ ├── ais3_crackme │ └── ReadMe.md └── Backdoor_CTF _no-calm │ ├── challenge │ └── ReadMe.md ├── angrgdb ├── __init__.py ├── explore.py ├── debugger.py ├── commands.py └── context_view.py ├── setup.py ├── LICENSE ├── .gitignore └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | angrdbg 2 | -------------------------------------------------------------------------------- /examples/crackme/crackmenoptrace: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreafioraldi/angrgdb/HEAD/examples/crackme/crackmenoptrace -------------------------------------------------------------------------------- /examples/ais3crackme/ais3_crackme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreafioraldi/angrgdb/HEAD/examples/ais3crackme/ais3_crackme -------------------------------------------------------------------------------- /examples/Backdoor_CTF _no-calm/challenge: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreafioraldi/angrgdb/HEAD/examples/Backdoor_CTF _no-calm/challenge -------------------------------------------------------------------------------- /angrgdb/__init__.py: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # Author: Andrea Fioraldi # 3 | # License: BSD 2-Clause # 4 | ###################################################### 5 | 6 | from .debugger import * 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Andrea Fioraldi" 4 | __copyright__ = "Copyright 2017, Andrea Fioraldi" 5 | __license__ = "BSD 2-Clause" 6 | __email__ = "andreafioraldi@gmail.com" 7 | 8 | from setuptools import setup 9 | 10 | VER = "1.0.14" 11 | 12 | setup( 13 | name='angrgdb', 14 | version=VER, 15 | license=__license__, 16 | description='Use angr inside GDB. Create an angr state from the current debugger state. ', 17 | author=__author__, 18 | author_email=__email__, 19 | url='https://github.com/andreafioraldi/angrgdb', 20 | download_url = 'https://github.com/andreafioraldi/angrgdb/archive/' + VER + '.tar.gz', 21 | package_dir={'angrgdb': 'angrgdb'}, 22 | packages=['angrgdb'], 23 | install_requires=['angrdbg'], 24 | ) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Andrea Fioraldi 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio Code 2 | .vscode/ 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | .static_storage/ 59 | .media/ 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | env/ 91 | venv/ 92 | ENV/ 93 | env.bak/ 94 | venv.bak/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | 109 | # Node 110 | 111 | .DS_Store 112 | node_modules/ 113 | dist/ 114 | npm-debug.log 115 | yarn-error.log 116 | 117 | # Editor directories and files 118 | .idea 119 | *.suo 120 | *.ntvs* 121 | *.njsproj 122 | *.sln 123 | 124 | # SQLite database 125 | *.sqlite3 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angrgdb 2 | 3 | Use angr inside GDB. Create an angr state from the current debugger state. 4 | 5 | ## Install 6 | 7 | ``` 8 | pip install angrgdb 9 | echo "python import angrgdb.commands" >> ~/.gdbinit 10 | ``` 11 | 12 | ## Usage 13 | 14 | angrgdb implements the [angrdbg](https://github.com/andreafioraldi/angrdbg) API in GDB. 15 | 16 | You can use it in scripts like this: 17 | 18 | ```python 19 | from angrgdb import * 20 | 21 | gdb.execute("b *0x004005f9") 22 | gdb.execute("r aaaaaaaa") 23 | 24 | sm = StateManager() 25 | sm.sim(sm["rax"], 100) 26 | 27 | m = sm.simulation_manager() 28 | m.explore(find=0x00400607, avoid=0x00400613) 29 | 30 | sm.to_dbg(m.found[0]) #write input to GDB 31 | 32 | gdb.execute("x/s $rax") 33 | #0x7fffffffe768: "ais3{I_tak3_g00d_n0t3s}" 34 | gdb.execute("c") 35 | #Correct! that is the secret key! 36 | ``` 37 | 38 | You can also use angrgdb commands directly in GDB for simple stuffs: 39 | 40 | + `angrgdb sim [size]` Symbolize a register 41 | + `angrgdb sim
[size]` Symbolize a memory area 42 | + `angrgdb list` List all items that you setted as symbolic 43 | + `angrgdb find ... ` Set the list of find targets 44 | + `angrgdb avoid ... ` Set the list of avoid targets 45 | + `angrgdb reset` Reset the context (symbolic values and targets) 46 | + `angrgdb run` Generate a state from the debugger state and run the exploration 47 | + `angrgdb shell` Open an shell with a StateManager instance created from the current GDB state 48 | + `angrgdb interactive` Generate a state from the debugger state and explore by hand using a modified version of [angr-cli](https://github.com/fmagin/angr-cli) 49 | 50 | An example crackme solve using angrgdb+GEF+[idb2gdb](https://github.com/andreafioraldi/idb2gdb): 51 | 52 | [![asciicast](https://asciinema.org/a/207571.png)](https://asciinema.org/a/207571) 53 | 54 | ### Loading scripts in GDB 55 | 56 | This is a tip if you don't want to use angrgdb from the cli but you want to use a python script. 57 | To load a script in GDB use `source script.py`. 58 | 59 | ## TODO 60 | 61 | + add remote angrdbg like in IDAngr 62 | 63 | ## Cite 64 | 65 | Thesis [PDF](https://arxiv.org/pdf/2006.16601.pdf). 66 | 67 | Bibtex: 68 | ``` 69 | @misc{fioraldi2020symbolic, 70 | title={Symbolic Execution and Debugging Synchronization}, 71 | author={Andrea Fioraldi}, 72 | year={2020}, 73 | eprint={2006.16601}, 74 | archivePrefix={arXiv}, 75 | primaryClass={cs.CR} 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /examples/Backdoor_CTF _no-calm/ReadMe.md: -------------------------------------------------------------------------------- 1 | On the main page for this project, there is an example in the form of an asciinema. The executable for that example comes from [BackdoorCTF 2017] 2 | 3 | Commands to run this example: 4 | ``` 5 | # Break AFTER all that setup and exception catching is done 6 | b *0x40085e 7 | # Start it up, give it a buffer of "0"s 8 | r 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 | # Tell angr to find the solution and avoid the failures 10 | angrgdb find 0x4007b6 11 | angrgdb avoid 0x4007cc 12 | # Tell angr to mark 30 bytes as symbolic 13 | angrgdb sim ($rbp-0x30) 30 14 | # Run! 15 | angrgdb run 16 | ``` 17 | Like this: 18 | ``` 19 | /Downloads/Backdoor_CTF _no-calm$ gdb ./challenge 20 | GNU gdb (Ubuntu 8.3-0ubuntu1) 8.3 21 | Copyright (C) 2019 Free Software Foundation, Inc. 22 | License GPLv3+: GNU GPL version 3 or later 23 | This is free software: you are free to change and redistribute it. 24 | There is NO WARRANTY, to the extent permitted by law. 25 | Type "show copying" and "show warranty" for details. 26 | This GDB was configured as "x86_64-linux-gnu". 27 | Type "show configuration" for configuration details. 28 | For bug reporting instructions, please see: 29 | . 30 | Find the GDB manual and other documentation resources online at: 31 | . 32 | 33 | For help, type "help". 34 | Type "apropos word" to search for commands related to "word"... 35 | Reading symbols from ./challenge... 36 | (No debugging symbols found in ./challenge) 37 | (gdb) b *0x40085e 38 | Breakpoint 1 at 0x40085e 39 | (gdb) r 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 40 | Starting program: /home/jan/Downloads/Backdoor_CTF _no-calm/challenge 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 41 | 42 | Breakpoint 1, 0x000000000040085e in main () 43 | (gdb) angrgdb sim ($rbp-0x30) 30 44 | WARNING | 2020-01-15 23:49:06,569 | angr.project | Disabling IRSB translation cache because support for self-modifying code is enabled. 45 | (gdb) angrgdb find 0x4007b6 46 | (gdb) angrgdb avoid 0x4007cc 47 | (gdb) angrgdb run 48 | >> to find: 0x4007b6 49 | >> to avoid: 0x4007cc 50 | >> running the exploration... 51 | WARNING | 2020-01-15 23:50:58,613 | angr.engines.vex.lifter | Self-modifying code is not always correctly optimized by PyVEX. To guarantee correctness, VEX optimizations have been disabled. 52 | >> results: 53 | 54 | 0x7fffffffde10 <30> 55 | ==> 'CTF{Now_th1s_1s_t0_g3t_ANGRyy}' 56 | 57 | 58 | >> do you want to write-back the results in GDB? [Y, n] >> syncing results with debugger... 59 | (gdb) c 60 | Continuing. 61 | hacked[Inferior 1 (process 6036) exited normally] 62 | (gdb) 63 | 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /examples/ais3crackme/ReadMe.md: -------------------------------------------------------------------------------- 1 | The executable is one of the standard angr executables available here: 2 | https://github.com/angr/angr-doc/tree/master/examples/ais3_crackme 3 | 4 | Commands to run this example: 5 | ``` 6 | # Break AFTER all that setup and exception catching is done 7 | b *0x004005f9 8 | # Start it up, give it a buffer of "a"s 9 | r aaaaaaaa 10 | # Tell angr to find the solution and avoid the failures 11 | angrgdb find 0x00400607 12 | angrgdb avoid 0x00400613 13 | # Tell angr to mark our 64 bytes as symbolic 14 | angrgdb sim $rax 100 15 | # Run! 16 | angrgdb run 17 | ``` 18 | Like this: 19 | ``` 20 | $ gdb ./ais3_crackme 21 | GNU gdb (Ubuntu 8.3-0ubuntu1) 8.3 22 | Copyright (C) 2019 Free Software Foundation, Inc. 23 | License GPLv3+: GNU GPL version 3 or later 24 | This is free software: you are free to change and redistribute it. 25 | There is NO WARRANTY, to the extent permitted by law. 26 | Type "show copying" and "show warranty" for details. 27 | This GDB was configured as "x86_64-linux-gnu". 28 | Type "show configuration" for configuration details. 29 | For bug reporting instructions, please see: 30 | . 31 | Find the GDB manual and other documentation resources online at: 32 | . 33 | 34 | For help, type "help". 35 | Type "apropos word" to search for commands related to "word"... 36 | Reading symbols from ./ais3_crackme... 37 | (No debugging symbols found in ./ais3_crackme) 38 | (gdb) b *0x004005f9 39 | Breakpoint 1 at 0x4005f9 40 | (gdb) r aaaaaaaa 41 | Starting program: /home/jan/Downloads/ais3_crackme aaaaaaaa 42 | 43 | Breakpoint 1, 0x00000000004005f9 in main () 44 | (gdb) angrgdb sim $rax 100 45 | WARNING | 2020-01-15 23:21:16,672 | angr.project | Disabling IRSB translation cache because support for self-modifying code is enabled. 46 | (gdb) angrgdb find 0x00400607 47 | (gdb) angrgdb avoid 0x00400613 48 | (gdb) angrgdb run 49 | >> to find: 0x400607 50 | >> to avoid: 0x400613 51 | >> running the exploration... 52 | WARNING | 2020-01-15 23:22:14,215 | angr.engines.vex.lifter | Self-modifying code is not always correctly optimized by PyVEX. To guarantee correctness, VEX optimizations have been disabled. 53 | >> results: 54 | 55 | 0x7fffffffe3d8 <100> 56 | ==> 'ais3{I_tak3_g00d_n0t3s}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 57 | 58 | 59 | >> do you want to write-back the results in GDB? [Y, n] >> syncing results with debugger... 60 | (gdb) c 61 | Continuing. 62 | Correct! that is the secret key! 63 | [Inferior 1 (process 27979) exited normally] 64 | (gdb) 65 | 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /examples/crackme/ReadMe.md: -------------------------------------------------------------------------------- 1 | This crackme and procedure is taken from this site: 2 | https://bannsecurity.com/index.php/home/10-ctf-writeups/51-utctf-2019-crackme 3 | The only difference is that I already patched out the ptrace test, since this example is just to show the functionality of angrgdb 4 | 5 | Commands to run this example: 6 | ``` 7 | # Break AFTER all that setup and exception catching is done 8 | break *0x400C96 9 | # Start it up 10 | r 11 | # Give it a buffer of 63 "A"s 12 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 13 | # Tell angr to find the solution and avoid the failures 14 | angrgdb find 0x400DAC 15 | angrgdb avoid 0x400DC8 16 | # Tell angr to mark our 64 bytes as symbolic 17 | angrgdb sim $rbp-0x50 0x40 18 | # Run! 19 | angrgdb run 20 | ``` 21 | Like this: 22 | ``` 23 | $ gdb ./crackmenoptrace 24 | GNU gdb (Ubuntu 8.3-0ubuntu1) 8.3 25 | Copyright (C) 2019 Free Software Foundation, Inc. 26 | License GPLv3+: GNU GPL version 3 or later 27 | This is free software: you are free to change and redistribute it. 28 | There is NO WARRANTY, to the extent permitted by law. 29 | Type "show copying" and "show warranty" for details. 30 | This GDB was configured as "x86_64-linux-gnu". 31 | Type "show configuration" for configuration details. 32 | For bug reporting instructions, please see: 33 | . 34 | Find the GDB manual and other documentation resources online at: 35 | . 36 | 37 | For help, type "help". 38 | Type "apropos word" to search for commands related to "word"... 39 | Reading symbols from ./crackmenoptrace... 40 | (No debugging symbols found in ./crackmenoptrace) 41 | (gdb) break *0x400C96 42 | Breakpoint 1 at 0x400c96 43 | (gdb) r 44 | Starting program: /home/jan/Downloads/crackmenoptrace 45 | [Thread debugging using libthread_db enabled] 46 | Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 47 | Please enter the correct password. 48 | >AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 49 | 50 | Breakpoint 1, 0x0000000000400c96 in main () 51 | (gdb) 52 | (gdb) angrgdb find 0x400DAC 53 | (gdb) angrgdb avoid 0x400DC8 54 | (gdb) angrgdb sim $rbp-0x50 0x40 55 | WARNING | 2020-01-15 07:29:06,778 | cle.backends.externs | Symbol was allocated without a known size; emulation will fail if it is used non-opaquely: _ZTIi 56 | WARNING | 2020-01-15 07:29:06,780 | cle.loader | For more information about "Symbol was allocated without a known size", see https://docs.angr.io/extending-angr/environment#simdata 57 | WARNING | 2020-01-15 07:29:06,823 | cle.backends.externs | Symbol was allocated without a known size; emulation will fail if it is used non-opaquely: _ZTIi 58 | WARNING | 2020-01-15 07:29:06,824 | cle.loader | For more information about "Symbol was allocated without a known size", see https://docs.angr.io/extending-angr/environment#simdata 59 | WARNING | 2020-01-15 07:29:06,824 | angr.project | Disabling IRSB translation cache because support for self-modifying code is enabled. 60 | (gdb) angrgdb run 61 | >> to find: 0x400dac 62 | >> to avoid: 0x400dc8 63 | >> running the exploration... 64 | WARNING | 2020-01-15 07:29:13,557 | angr.engines.vex.lifter | Self-modifying code is not always correctly optimized by PyVEX. To guarantee correctness, VEX optimizations have been disabled. 65 | >> results: 66 | 67 | 0x7fffffffde30 <64> 68 | ==> '1_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3st\x1b0f_cspp3rstick6\x00' 69 | 70 | 71 | >> do you want to write-back the results in GDB? [Y, n] >> syncing results with debugger... 72 | (gdb) 73 | 74 | ``` 75 | Thanks to Michael Bann! 76 | 77 | -------------------------------------------------------------------------------- /angrgdb/explore.py: -------------------------------------------------------------------------------- 1 | 2 | from cmd import Cmd 3 | 4 | 5 | class GUICallbackBaseClass(): 6 | def update_ip(self, ip): 7 | pass 8 | 9 | 10 | class BinjaCallback(GUICallbackBaseClass): 11 | def __init__(self, bv): 12 | self.bv = bv 13 | 14 | def update_ip(self, ip): 15 | self.bv.file.navigate(self.bv.file.view, ip) 16 | 17 | 18 | def red(text): 19 | return "\x1b[0;31m" + text + "\x1b[0m" 20 | 21 | class ExploreInteractive(Cmd, object): 22 | 23 | intro = red("[!] Dropping into angr shell\n") 24 | intro += red("Available Commands: print, pyshell, (p)ick, (r)un, (s)tep, stepi, (q)uit") 25 | prompt = red(">>> ") 26 | 27 | def __init__(self, proj, state, gui_callback_object=GUICallbackBaseClass()): 28 | super(ExploreInteractive, self).__init__() 29 | self.proj = proj 30 | self.simgr = proj.factory.simulation_manager(state) 31 | if "deferred" not in self.simgr.stashes: 32 | self.simgr.stashes["deferred"] = [] 33 | self.gui_cb = gui_callback_object 34 | 35 | @property 36 | def state(self): 37 | """ 38 | Alias to `self.simgr.one_active` 39 | :return: 40 | """ 41 | return self.simgr.one_active 42 | 43 | def _clearScreen(self): 44 | print("\033[H\033[J") 45 | 46 | def do_quit(self, args): 47 | """Quits the cli.""" 48 | print(red("Exiting cmd-loop")) 49 | return True 50 | 51 | def do_q(self, args): 52 | self.do_quit(args) 53 | return True 54 | 55 | def do_print(self, arg): 56 | """ 57 | print [state_number] 58 | Prints a state 59 | state_number optionally specifies the state to print if multiple are available 60 | """ 61 | if not arg: 62 | arg = "0" 63 | 64 | pick = int(arg) 65 | active = len(self.simgr.active) 66 | if pick >= active: 67 | print(red("Only {} active state(s), indexed from 0".format(active))) 68 | else: 69 | self.simgr.active[pick].context_view.pprint() 70 | self.gui_cb.update_ip(self.simgr.active[pick].addr) 71 | 72 | def do_stepi(self, args): 73 | """ 74 | stepi 75 | Steps one instruction 76 | """ 77 | if len(self.simgr.active) == 1: 78 | self.simgr.step(num_inst=1) 79 | self._clearScreen() 80 | self.simgr.one_active.context_view.pprint() 81 | self.gui_cb.update_ip(self.simgr.one_active.addr) 82 | elif len(self.simgr.active) > 1: 83 | for idx, state in enumerate(self.simgr.active): 84 | print(state.context_view.pstr_branch_info(idx)) 85 | 86 | def do_step(self, args): 87 | """ 88 | step 89 | Steps the current state one basic block 90 | """ 91 | if len(self.simgr.active) == 1: 92 | self.simgr.step() 93 | self._clearScreen() 94 | self.simgr.one_active.context_view.pprint() 95 | self.gui_cb.update_ip(self.simgr.one_active.addr) 96 | elif len(self.simgr.active) > 1: 97 | for idx, state in enumerate(self.simgr.active): 98 | print(state.context_view.pstr_branch_info(idx)) 99 | 100 | def do_s(self, args): 101 | self.do_step(args) 102 | 103 | def do_s(self, args): 104 | self.do_step(args) 105 | 106 | def do_run(self, args): 107 | """ 108 | run [state_number] 109 | Runs until a branch is encountered 110 | state_number optionally picks a state if multiple are available 111 | """ 112 | if len(self.simgr.active) > 1 and args: 113 | self.do_pick(args) 114 | if len(self.simgr.active) == 1: 115 | self.simgr.run(until=lambda s: len(s.active) != 1) 116 | if self.simgr.active: 117 | self.gui_cb.update_ip(self.simgr.one_active.addr) 118 | 119 | if len(self.simgr.active) > 0: 120 | for i, state in enumerate(self.simgr.active): 121 | print(state.context_view.pstr_branch_info(i)) 122 | else: 123 | print(red("STATE FINISHED EXECUTION")) 124 | if len(self.simgr.stashes["deferred"]) == 0: 125 | print(red("No states left to explore")) 126 | else: # DFS-style like 127 | print(red("Other side of last branch has been added to {}".format(self.simgr))) 128 | self.simgr.stashes["active"].append(self.simgr.stashes["deferred"].pop()) 129 | 130 | def do_r(self, args): 131 | self.do_run(args) 132 | 133 | 134 | def do_pick(self, arg): 135 | """ 136 | pick 137 | Selects a state to continue if multiple are available, the other state is saved 138 | """ 139 | try: 140 | pick = int(arg) 141 | ip = self.simgr.active[pick].regs.ip 142 | except: 143 | print("Invalid Choice: "+red("{}".format(arg))+", for {}".format(self.simgr)) 144 | return False 145 | print(red("Picking state with ip: " + (str(ip)))) 146 | self.simgr.move(from_stash='active', 147 | to_stash="deferred", 148 | filter_func=lambda x: x.solver.eval(ip != x.regs.ip)) 149 | self.simgr.step() 150 | self._clearScreen() 151 | self.simgr.one_active.context_view.pprint() 152 | 153 | def do_p(self, args): 154 | self.do_pick(args) 155 | 156 | def do_pyshell(self, args): 157 | import gdb 158 | gdb.execute('pi') 159 | 160 | def do_EOF(self, args): 161 | self.do_quit(args) 162 | return True 163 | 164 | -------------------------------------------------------------------------------- /angrgdb/debugger.py: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # Author: Andrea Fioraldi # 3 | # License: BSD 2-Clause # 4 | ###################################################### 5 | 6 | import sys 7 | import struct 8 | import re 9 | 10 | try: 11 | import gdb 12 | except ImportError: 13 | print("angrgdb: fatal error: not running inside GDB") 14 | exit(1) 15 | 16 | if sys.version_info >= (3, 0): 17 | long = int 18 | else: 19 | long 20 | bytes = str 21 | 22 | from angrdbg import * 23 | 24 | 25 | class GDBDebugger(Debugger): 26 | def __init__(self): 27 | self.inferior = gdb.selected_inferior() 28 | self.pid = 0 29 | self.base_addr = None 30 | self.efl_map = {} 31 | self.efl_map['CF'] = 1 << 0 32 | self.efl_map['PF'] = 1 << 2 33 | self.efl_map['AF'] = 1 << 4 34 | self.efl_map['ZF'] = 1 << 6 35 | self.efl_map['SF'] = 1 << 7 36 | self.efl_map['TF'] = 1 << 8 37 | self.efl_map['IF'] = 1 << 9 38 | self.efl_map['DF'] = 1 << 10 39 | self.efl_map['OF'] = 1 << 11 40 | 41 | def _get_vmmap(self): 42 | maps = [] 43 | if self.pid == 0: 44 | pid = self.inferior.pid 45 | else: 46 | pid = self.pid 47 | 48 | mpath = "/proc/%s/maps" % pid 49 | # 00400000-0040b000 r-xp 00000000 08:02 538840 /path/to/file 50 | pattern = re.compile( 51 | "([0-9a-f]*)-([0-9a-f]*) ([rwxps-]*)(?: [^ ]*){3} *(.*)") 52 | 53 | out = open(mpath).read() 54 | 55 | matches = pattern.findall(out) 56 | if matches: 57 | for (start, end, perm, mapname) in matches: 58 | start = int(("0x%s" % start), 0) 59 | end = int(("0x%s" % end), 0) 60 | if mapname == "": 61 | mapname = "mapped" 62 | mapperm = 0 63 | if "r" in perm: 64 | mapperm |= SEG_PROT_R 65 | if "w" in perm: 66 | mapperm |= SEG_PROT_W 67 | if "x" in perm: 68 | mapperm |= SEG_PROT_X 69 | maps += [(start, end, mapperm, mapname)] 70 | return maps 71 | 72 | def _get_sections(self): 73 | base = self.image_base() 74 | info = gdb.execute("info file", to_string=True) 75 | # 0x0000000000000238 - 0x0000000000000254 is .interp 76 | pattern = re.compile("0x([0-9a-f]*) - 0x([0-9a-f]*) is (.*)") 77 | 78 | matches = pattern.findall(info) 79 | # don't get sections of shared libs 80 | matches = filter(lambda x: " in " not in x[2], matches) 81 | return map(lambda x: (int(x[0], 16), int(x[1], 16), x[2]), matches) 82 | 83 | # ------------------------------------- 84 | def before_stateshot(self): 85 | self.vmmap = self._get_vmmap() 86 | self.base_addr = self.vmmap[0][0] 87 | sections = self._get_sections() 88 | 89 | for start, end, name in sections: 90 | if name == load_project().arch.got_section_name: 91 | self.got = (start, end) 92 | elif name == ".plt": 93 | self.plt = (start, end) 94 | elif name == ".idata": 95 | self.plt = (start, end) 96 | self.long_type = gdb.lookup_type("long") 97 | 98 | def after_stateshot(self, state): 99 | pass 100 | # ------------------------------------- 101 | 102 | def is_active(self): 103 | return gdb.selected_thread() is not None 104 | 105 | # ------------------------------------- 106 | def input_file(self): 107 | return gdb.current_progspace().filename 108 | #return open(gdb.current_progspace().filename, "rb") 109 | 110 | def image_base(self): 111 | if self.base_addr is None: 112 | self.before_stateshot() 113 | return self.base_addr 114 | 115 | # ------------------------------------- 116 | def get_byte(self, addr): 117 | try: 118 | return int(self.inferior.read_memory(addr, 1).tobytes()[0]) 119 | except BaseException: 120 | return None 121 | 122 | def get_word(self, addr): 123 | try: 124 | return struct.unpack( 125 | "> 128) & 0xffffffffffffffffffffffffffffffff 193 | gdb.execute("set $%s.v2_int128[0] = %d" % (name, v0)) 194 | gdb.execute("set $%s.v2_int128[1] = %d" % (name, v1)) 195 | else: 196 | gdb.execute("set $%s = %d" % (name, value)) 197 | 198 | # ------------------------------------- 199 | def step_into(self): 200 | gdb.execute("stepi", to_string=True) 201 | 202 | def run(self): 203 | gdb.execute("continue") 204 | 205 | def wait_ready(self): 206 | pass 207 | 208 | def refresh_memory(self): 209 | pass 210 | 211 | # ------------------------------------- 212 | def seg_by_name(self, name): 213 | for start, end, perms, mname in self.vmmap: 214 | if name == mname: 215 | return Segment(name, start, end, perms) 216 | return None 217 | 218 | def seg_by_addr(self, addr): 219 | for start, end, perms, name in self.vmmap: 220 | if addr >= start and addr < end: 221 | return Segment(name, start, end, perms) 222 | return None 223 | 224 | def get_got(self): # return tuple(start_addr, end_addr) 225 | return self.got 226 | 227 | def get_plt(self): # return tuple(start_addr, end_addr) 228 | return self.plt 229 | 230 | def get_idata(self): # return tuple(start_addr, end_addr) 231 | return self.idata 232 | 233 | # ------------------------------------- 234 | def resolve_name(self, name): # return None on fail 235 | try: 236 | res = gdb.execute("info address " + name, to_string=True) 237 | a = res.find(" is at 0x") 238 | b = res.find(" ", a + len(" is at 0x")) 239 | return int(res[a + len(" is at 0x"):b], 16) 240 | except BaseException: 241 | return None 242 | 243 | 244 | register_debugger(GDBDebugger()) 245 | -------------------------------------------------------------------------------- /angrgdb/commands.py: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # Author: Andrea Fioraldi # 3 | # License: BSD 2-Clause # 4 | ###################################################### 5 | 6 | import gdb 7 | import angr 8 | 9 | '''try: 10 | import IPython 11 | has_shell = True 12 | except: 13 | has_shell = False''' 14 | 15 | from angrgdb import * 16 | 17 | import sys 18 | if sys.version_info >= (3, 0): 19 | long = int 20 | raw_input = input 21 | else: 22 | long 23 | bytes = str 24 | 25 | 26 | BANNER = " >>" 27 | 28 | class AngrGDBError(RuntimeError): 29 | pass 30 | 31 | 32 | def _to_int(x): 33 | try: 34 | return int(gdb.parse_and_eval(x).cast(gdb.lookup_type("long"))) 35 | except BaseException as e: 36 | print (e) 37 | return None 38 | 39 | def _prepare_args(args): 40 | return args.replace("\\", "\\\\").replace("\"", "\\\"") 41 | 42 | def _prepare_shell(loc): 43 | p = load_project(support_selfmodifying_code=True) 44 | sm = StateManager(sync_brk=False) 45 | for k in _ctx.symbolics: 46 | if _ctx.symbolics[k] is None: 47 | sm.sim(k) 48 | else: 49 | sm.sim(k, _ctx.symbolics[k]) 50 | loc["sm"] = sm 51 | loc["m"] = sm.simulation_manager() 52 | 53 | loc["find"] = _ctx.find 54 | loc["avoid"] = _ctx.avoid 55 | 56 | 57 | class CommandsContext(object): 58 | def __init__(self): 59 | self.symbolics = {} 60 | self.find = [] 61 | self.avoid = [] 62 | 63 | 64 | _ctx = CommandsContext() 65 | 66 | 67 | class AngrGDBCommand(gdb.Command): 68 | ''' 69 | Symbolic execution in GDB with angrdbg 70 | ''' 71 | 72 | def __init__(self): 73 | super(AngrGDBCommand, self).__init__("angrgdb", gdb.COMMAND_USER, gdb.COMPLETE_NONE, True) 74 | 75 | 76 | class AngrGDBShellCommand(gdb.Command): 77 | ''' 78 | Open a python shell with a StateManager instance 79 | ''' 80 | 81 | def __init__(self): 82 | super(AngrGDBShellCommand, self).__init__("angrgdb shell", gdb.COMMAND_USER) 83 | 84 | def invoke(self, arg, from_tty): 85 | global _ctx 86 | self.dont_repeat() 87 | 88 | if not from_tty: 89 | raise AngrGDBError("The angrgdb shell can be launched only from the tty") 90 | 91 | print (BANNER + " sm is a StateManager instance created from the current GDB state") 92 | print (BANNER + " m is a SimulationManager based on the sm state") 93 | print (BANNER + " find and avoid are the list of addresses") 94 | print (BANNER + " to find:", ", ".join(map(lambda x: "0x%x" % x, _ctx.find))) 95 | print (BANNER + " to avoid:", ", ".join(map(lambda x: "0x%x" % x, _ctx.avoid))) 96 | 97 | gdb.execute("py from angrgdb.commands import _prepare_shell; _prepare_shell(locals()); gdb.execute('pi')") 98 | 99 | 100 | class AngrGDBResetCommand(gdb.Command): 101 | ''' 102 | Reset the context fo angrgdb 103 | ''' 104 | 105 | def __init__(self): 106 | super( 107 | AngrGDBResetCommand, self).__init__("angrgdb reset", gdb.COMMAND_USER) 108 | 109 | def invoke(self, arg, from_tty): 110 | global _ctx 111 | self.dont_repeat() 112 | 113 | _ctx = CommandsContext() 114 | 115 | 116 | class AngrGDBSimCommand(gdb.Command): 117 | ''' 118 | Set a memory/register as symbolic 119 | 120 | Usage: angrgdb sim [size] 121 | angrgdb sim [size] 122 | ''' 123 | 124 | def __init__(self): 125 | super( 126 | AngrGDBSimCommand, 127 | self).__init__( 128 | "angrgdb sim", 129 | gdb.COMMAND_DATA) 130 | 131 | def _process_argv0(self, x): 132 | if x in load_project(support_selfmodifying_code=True).arch.registers: 133 | return x 134 | r = _to_int(x) 135 | if r: 136 | return r 137 | raise AngrGDBError( 138 | "angrdbg sim: the first parameter is not an address or a register") 139 | 140 | def invoke(self, arg, from_tty): 141 | global _ctx 142 | self.dont_repeat() 143 | 144 | argv = gdb.string_to_argv(_prepare_args(arg)) 145 | if len(argv) == 0: 146 | raise AngrGDBError("angrdbg sim: at least a parameter is needed") 147 | elif len(argv) == 1: 148 | _ctx.symbolics[self._process_argv0(argv[0])] = None 149 | else: 150 | siz = _to_int(argv[1]) 151 | if siz is None: 152 | raise AngrGDBError( 153 | "angrdbg sim: the second parameter (length) must be a number") 154 | _ctx.symbolics[self._process_argv0(argv[0])] = siz 155 | 156 | 157 | class AngrGDBListCommand(gdb.Command): 158 | ''' 159 | List all items that you setted as symbolic 160 | ''' 161 | 162 | def __init__(self): 163 | super( 164 | AngrGDBListCommand, 165 | self).__init__( 166 | "angrgdb list", 167 | gdb.COMMAND_DATA) 168 | 169 | def invoke(self, arg, from_tty): 170 | global _ctx 171 | self.dont_repeat() 172 | 173 | for k in _ctx.symbolics: 174 | out = k 175 | if isinstance(k, int): 176 | out = "0x%x" % k 177 | if _ctx.symbolics[k] is not None: 178 | out += " " * (20 - len(out)) + "<%d>" % _ctx.symbolics[k] 179 | print (out) 180 | 181 | 182 | class AngrGDBFindCommand(gdb.Command): 183 | ''' 184 | Set the list of find targets 185 | 186 | Usage: angrgdb find ... 187 | ''' 188 | 189 | def __init__(self): 190 | super( 191 | AngrGDBFindCommand, 192 | self).__init__( 193 | "angrgdb find", 194 | gdb.COMMAND_DATA) 195 | 196 | def invoke(self, arg, from_tty): 197 | global _ctx 198 | self.dont_repeat() 199 | 200 | argv = gdb.string_to_argv(_prepare_args(arg)) 201 | if len(argv) == 0: 202 | raise AngrGDBError("angrdbg find: at least a parameter is needed") 203 | 204 | _ctx.find = [] 205 | for a in argv: 206 | addr = _to_int(a) 207 | if addr is None: 208 | raise AngrGDBError( 209 | "angrdbg find: failed to convert '%s' to int" % 210 | a) 211 | _ctx.find.append(addr) 212 | 213 | 214 | class AngrGDBAvoidCommand(gdb.Command): 215 | ''' 216 | Set the list of avoid targets 217 | 218 | Usage: angrgdb avoid ... 219 | ''' 220 | 221 | def __init__(self): 222 | super( 223 | AngrGDBAvoidCommand, 224 | self).__init__( 225 | "angrgdb avoid", 226 | gdb.COMMAND_DATA) 227 | 228 | def invoke(self, arg, from_tty): 229 | global _ctx 230 | self.dont_repeat() 231 | 232 | argv = gdb.string_to_argv(_prepare_args(arg)) 233 | if len(argv) == 0: 234 | raise AngrGDBError("angrdbg avoid: at least a parameter is needed") 235 | 236 | _ctx.avoid = [] 237 | for a in argv: 238 | addr = _to_int(a) 239 | if addr is None: 240 | raise AngrGDBError( 241 | "angrdbg avoid: failed to convert '%s' to int" % 242 | a) 243 | _ctx.avoid.append(addr) 244 | 245 | 246 | class AngrGDBRunCommand(gdb.Command): 247 | ''' 248 | Generate a state from the debugger state and run the exploration 249 | ''' 250 | 251 | def __init__(self): 252 | super( 253 | AngrGDBRunCommand, 254 | self).__init__( 255 | "angrgdb run", 256 | gdb.COMMAND_DATA) 257 | 258 | def invoke(self, arg, from_tty): 259 | global _ctx 260 | self.dont_repeat() 261 | 262 | if len(_ctx.find) == 0: 263 | raise AngrGDBError("angrdbg run: the find list can't be empty") 264 | 265 | print (BANNER + " to find:", ", ".join(map(lambda x: "0x%x" % x, _ctx.find))) 266 | print (BANNER + " to avoid:", ", ".join(map(lambda x: "0x%x" % x, _ctx.avoid))) 267 | 268 | p = load_project(support_selfmodifying_code=True) 269 | sm = StateManager(sync_brk=False) 270 | for o in angr.options.unicorn: sm.state.options.add(o) 271 | 272 | for k in _ctx.symbolics: 273 | if _ctx.symbolics[k] is None: 274 | sm.sim(k) 275 | else: 276 | sm.sim(k, _ctx.symbolics[k]) 277 | m = sm.simulation_manager() 278 | 279 | print (BANNER + " running the exploration...") 280 | m.explore(find=_ctx.find, avoid=_ctx.avoid) 281 | if len(m.found) == 0: 282 | raise AngrGDBError( 283 | "angrdbg run: valid state not found after exploration") 284 | 285 | conc = sm.concretize(m.found[0]) 286 | print (BANNER + " results:\n") 287 | for k in _ctx.symbolics: 288 | out = k 289 | if isinstance(k, int): 290 | out = "0x%x" % k 291 | if _ctx.symbolics[k] is not None: 292 | out += " " * (20 - len(out)) + "<%d>" % _ctx.symbolics[k] 293 | print (out) 294 | out = conc[k] 295 | if isinstance(out, (int, long)): 296 | print (" ==> 0x%x" % out) 297 | else: 298 | ro = repr(out) 299 | print (" ==> %s" % ro[1:] if ro.startswith("b") else ro) 300 | print () 301 | 302 | r = raw_input(BANNER + " do you want to write-back the results in GDB? [Y, n] ") 303 | r = r.strip().upper() 304 | if r == "Y" or r == "": 305 | print (BANNER + " syncing results with debugger...") 306 | sm.to_dbg(m.found[0]) 307 | 308 | 309 | class AngrGDBInteractiveCommand(gdb.Command): 310 | ''' 311 | Generate a state from the debugger state and run the exploration interatively using a custom version of angr-cli 312 | ''' 313 | 314 | def __init__(self): 315 | super( 316 | AngrGDBInteractiveCommand, 317 | self).__init__( 318 | "angrgdb interactive", 319 | gdb.COMMAND_DATA) 320 | 321 | def invoke(self, arg, from_tty): 322 | global _ctx 323 | self.dont_repeat() 324 | 325 | from . import context_view 326 | from . import explore 327 | 328 | print (BANNER + " to find:", ", ".join(map(lambda x: "0x%x" % x, _ctx.find))) 329 | print (BANNER + " to avoid:", ", ".join(map(lambda x: "0x%x" % x, _ctx.avoid))) 330 | try: 331 | p = load_project(support_selfmodifying_code=True) 332 | sm = StateManager(sync_brk=False) 333 | sm.state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY) 334 | sm.state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS) 335 | for o in angr.options.unicorn: sm.state.options.add(o) 336 | 337 | for k in _ctx.symbolics: 338 | if _ctx.symbolics[k] is None: 339 | sm.sim(k) 340 | else: 341 | sm.sim(k, _ctx.symbolics[k]) 342 | m = sm.simulation_manager() 343 | 344 | print (BANNER + " running the exploration...") 345 | 346 | 347 | e = explore.ExploreInteractive(p, sm.state) 348 | e.cmdloop() 349 | except: 350 | import traceback 351 | traceback.print_exc() 352 | raise AngrGDBError( 353 | "angrdbg interactive: error in angr-cli exploration") 354 | 355 | if "found" not in e.simgr.stashes or len(e.simgr.found) == 0: 356 | raise AngrGDBError( 357 | "angrdbg interactive: valid state not found after exploration") 358 | 359 | conc = sm.concretize(e.simgr.found[0]) 360 | print (BANNER + " results:\n") 361 | for k in _ctx.symbolics: 362 | out = k 363 | if isinstance(k, int): 364 | out = "0x%x" % k 365 | if _ctx.symbolics[k] is not None: 366 | out += " " * (20 - len(out)) + "<%d>" % _ctx.symbolics[k] 367 | print (out) 368 | out = conc[k] 369 | if isinstance(out, (int, long)): 370 | print (" ==> 0x%x" % out) 371 | else: 372 | ro = repr(out) 373 | print (" ==> %s" % ro[1:] if ro.startswith("b") else ro) 374 | print () 375 | 376 | r = raw_input(BANNER + " do you want to write-back the results in GDB? [Y, n] ") 377 | r = r.strip().upper() 378 | if r == "Y" or r == "": 379 | print (BANNER + " syncing results with debugger...") 380 | sm.to_dbg(m.found[0]) 381 | 382 | 383 | AngrGDBCommand() 384 | AngrGDBShellCommand() 385 | AngrGDBResetCommand() 386 | AngrGDBSimCommand() 387 | AngrGDBListCommand() 388 | AngrGDBFindCommand() 389 | AngrGDBAvoidCommand() 390 | AngrGDBRunCommand() 391 | AngrGDBInteractiveCommand() 392 | -------------------------------------------------------------------------------- /angrgdb/context_view.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import capstone 3 | from angr import SimEngineError 4 | from angr.calling_conventions import SimFunctionArgument 5 | from angr.sim_type import * 6 | 7 | from angr.storage.paged_memory import BasePage 8 | from angr.state_plugins import SimStatePlugin 9 | 10 | l = logging.getLogger('angr.state_plugins.context_view') 11 | 12 | from pygments import highlight 13 | from pygments.lexers import NasmLexer 14 | from pygments.formatters import TerminalFormatter 15 | 16 | from angrgdb import * 17 | 18 | class DisassemblerInterface(): 19 | def disass_block(self, block) -> [str]: 20 | raise NotImplemented 21 | 22 | 23 | class AngrCapstoneDisassembler(DisassemblerInterface): 24 | def disass_block(self, block): 25 | """ 26 | 27 | :param angr.block.Block block: 28 | :return: 29 | """ 30 | return str(block.capstone) 31 | 32 | import sys 33 | if sys.version_info >= (3, 0): 34 | long = int 35 | else: 36 | bytes = str 37 | 38 | MAX_AST_DEPTH = 5 39 | MAX_DISASS_LENGHT = 30 40 | 41 | headerWatch = "[ ──────────────────────────────────────────────────────────────────── Watches ── ]" 42 | headerBacktrace = "[ ────────────────────────────────────────────────────────────────── BackTrace ── ]" 43 | headerCode = "[ ─────────────────────────────────────────────────────────────────────── Code ── ]" 44 | headerFDs = "[ ──────────────────────────────────────────────────────────── FileDescriptors ── ]" 45 | headerStack = "[ ────────────────────────────────────────────────────────────────────── Stack ── ]" 46 | headerRegs = "[ ────────────────────────────────────────────────────────────────── Registers ── ]" 47 | class ContextView(SimStatePlugin): 48 | 49 | # Class variable to specify disassembler 50 | _disassembler = AngrCapstoneDisassembler() 51 | def __init__(self): 52 | super(ContextView, self).__init__() 53 | 54 | def cap_disasm(self, ip): 55 | md = capstone.Cs(self.state.project.arch.cs_arch, self.state.project.arch.cs_mode) 56 | r = "" 57 | code = self.state.memory.load(ip, MAX_DISASS_LENGHT*10) 58 | code = self.state.solver.eval(code, cast_to=bytes) 59 | cnt = 0 60 | for i in md.disasm(code, MAX_DISASS_LENGHT*10): 61 | r += "0x%x:\t%s\t%s\n" % (ip + i.address, i.mnemonic, i.op_str) 62 | cnt += 1 63 | if cnt == 18: break 64 | return highlight(r, NasmLexer(), TerminalFormatter()) 65 | 66 | def set_state(self, state): 67 | super(ContextView, self).set_state(state) 68 | self.stack = Stack(self.state) 69 | 70 | @SimStatePlugin.memo 71 | def copy(self, memo): 72 | return ContextView() 73 | 74 | def red(self, text): 75 | return "\x1b[0;31m" + text + "\x1b[0m" 76 | 77 | def blue(self, text): 78 | return "\x1b[0;34m" + text + "\x1b[0m" 79 | 80 | def green(self, text): 81 | return "\x1b[0;32m" + text + "\x1b[0m" 82 | 83 | def yellow(self, text): 84 | return "\x1b[0;33m" + text + "\x1b[0m" 85 | 86 | def magenta(self, text): 87 | return "\x1b[0;35m" + text + "\x1b[0m" 88 | 89 | def underline(self, text): 90 | return "\x1b[4m" + text + "\x1b[0m" 91 | 92 | def grey(self, text): 93 | return "\x1b[40;90;32m" + text + "\x1b[0m" 94 | 95 | def BVtoREG(self, bv): 96 | if type(bv) == str: 97 | return bv 98 | if "reg" in str(bv): 99 | replname = str(bv) 100 | for v in self.state.solver.describe_variables(bv): 101 | if "reg" in v: 102 | ridx = v[1] 103 | regname = self.state.arch.register_names[ridx] 104 | replname = replname.replace("reg_" + hex(ridx)[2:], regname) 105 | return replname 106 | return str(bv) 107 | 108 | def print_legend(self): 109 | s = "LEGEND: " 110 | s += self.green("SYMBOLIC") 111 | s += " | " + self.grey("UNINITIALIZED") 112 | s += " | " + self.yellow("STACK") 113 | s += " | " + self.blue("HEAP") 114 | s += " | " + self.red("CODE R-X") 115 | s += " | " + self.magenta("DATA R*-") 116 | s += " | " + self.underline("RWX") 117 | s += " | RODATA" 118 | print(s) 119 | 120 | def cc(self, bv): 121 | """Takes a BV and returns a colored string""" 122 | if bv.symbolic: 123 | if bv.uninitialized: 124 | return self.grey(self.BVtoREG(bv)) 125 | return self.green(self.BVtoREG(bv)) 126 | # its concrete 127 | value = self.state.solver.eval(bv, cast_to=int) 128 | try: 129 | perm = self.state.memory.permissions(value) 130 | if perm: 131 | if perm & BasePage.PROT_EXEC: 132 | descr = " <%s>" % self.state.project.loader.describe_addr(value) 133 | if descr == 'not part of a loaded object': 134 | return self.red(hex(value)) 135 | return self.red(hex(value) + descr) 136 | else: 137 | descr = " <%s>" % self.state.project.loader.describe_addr(value) 138 | if descr == 'not part of a loaded object': 139 | return self.magenta(hex(value)) 140 | return self.magenta(hex(value) + descr) 141 | except: 142 | pass 143 | 144 | if self.state.solver.eval(self.state.regs.sp) <= value < self.state.arch.initial_sp: 145 | return self.yellow(hex(value)) 146 | if self.state.heap.heap_base <= value <= self.state.heap.heap_location: 147 | return self.blue(hex(value)) 148 | return hex(value) 149 | 150 | def pprint(self): 151 | """Pretty context view similiar to the context view of gdb plugins (peda and pwndbg)""" 152 | self.print_legend() 153 | self.state.context_view.registers() 154 | self.state.context_view.code() 155 | self.state.context_view.fds() 156 | self.state.context_view.print_stack() 157 | self.state.context_view.print_backtrace() 158 | self.state.context_view.print_watches() 159 | return "" 160 | 161 | def print_backtrace(self): 162 | print(self.blue(headerBacktrace)) 163 | print("\n".join(self.__pstr_backtrace())) 164 | 165 | 166 | def __pstr_backtrace(self): 167 | result = [] 168 | for i, f in enumerate(self.state.callstack): 169 | if self.state.project.loader.find_object_containing(f.call_site_addr): 170 | call_site_addr = self.state.project.loader.describe_addr(f.call_site_addr) 171 | else: 172 | call_site_addr = "%#x" % f.call_site_addr 173 | if self.state.project.loader.find_object_containing(f.func_addr): 174 | func_addr = self.state.project.loader.describe_addr(f.func_addr) 175 | else: 176 | func_addr = "%#x" % f.func_addr 177 | 178 | frame = "Frame %d: %s => %s, sp = %#x" % (i, call_site_addr, func_addr, f.stack_ptr) 179 | 180 | result.append(frame) 181 | return result 182 | 183 | def code(self): 184 | print(self.blue(headerCode)) 185 | try: 186 | self.__print_previous_codeblock() 187 | print("\t|\t" + self.cc(self.state.solver.simplify(self.state.history.jump_guard)) + "\n\tv") 188 | except: 189 | pass 190 | self.__print_current_codeblock() 191 | 192 | def __print_previous_codeblock(self): 193 | prev_ip = self.state.history.bbl_addrs[-1] 194 | 195 | # Print the location (like main+0x10) if possible 196 | descr = self.state.project.loader.describe_addr(prev_ip) 197 | if descr != 'not part of a loaded object': 198 | print(descr) 199 | 200 | if not self.state.project.is_hooked(prev_ip): 201 | code = self.__pstr_codeblock(prev_ip) 202 | if code == None: 203 | raise Exception() 204 | code = code.split("\n") 205 | 206 | # if it is longer than MAX_DISASS_LENGTH, only print the first lines 207 | if len(code) >= MAX_DISASS_LENGHT: 208 | print("TRUNCATED BASIC BLOCK") 209 | print("\n".join(code[-MAX_DISASS_LENGHT:])) 210 | else: 211 | print("\n".join(code)) 212 | return 213 | else: 214 | hook = self.state.project.hooked_by(prev_ip) 215 | print(hook) 216 | 217 | 218 | def __print_current_codeblock(self): 219 | current_ip = self.state.solver.eval(self.state.regs.ip) 220 | 221 | # Print the location (like main+0x10) if possible 222 | descr = self.state.project.loader.describe_addr(current_ip) 223 | if descr != 'not part of a loaded object': 224 | print(descr) 225 | else: 226 | print("\n") 227 | 228 | # Check if we are at the start of a known function to maybe pretty print the arguments 229 | try: 230 | function = self.state.project.kb.functions[current_ip] 231 | except KeyError: 232 | pass 233 | else: 234 | if function.calling_convention: 235 | print("\n".join(self.pstr_call_info(self.state, function))) 236 | 237 | 238 | # Get the current code block about to be executed as pretty disassembly 239 | 240 | if not self.state.project.is_hooked(current_ip): 241 | code = self.__pstr_codeblock(current_ip) 242 | if code == None: 243 | code = self.cap_disasm(current_ip) 244 | code = code.split("\n") 245 | # if it is longer than MAX_DISASS_LENGTH, only print the first lines 246 | if len(code) >= MAX_DISASS_LENGHT: 247 | print("\n".join(code[:MAX_DISASS_LENGHT])) 248 | print("TRUNCATED BASIC BLOCK") 249 | else: 250 | print("\n".join(code)) 251 | else: 252 | hook = self.state.project.hooked_by(current_ip) 253 | print(hook) 254 | 255 | 256 | def __pstr_codeblock(self, ip) -> str: 257 | """Get the pretty version of a basic block with Pygemnts""" 258 | try: 259 | block = self.state.project.factory.block(ip) 260 | code = self._disassembler.disass_block(block) 261 | return highlight(code, NasmLexer(), TerminalFormatter()) 262 | except SimEngineError: 263 | return None 264 | 265 | 266 | 267 | 268 | def fds(self): 269 | if [b"", b"", b""] == [self.state.posix.dumps(x) for x in self.state.posix.fd]: 270 | return 271 | print(self.blue(headerFDs)) 272 | for fd in self.state.posix.fd: 273 | print("fd " + str(fd), ":", repr(self.state.posix.dumps(fd))) 274 | 275 | def print_stack(self): 276 | stackdepth = 8 277 | print(self.blue(headerStack)) 278 | # Not sure if that can happen, but if it does things will break 279 | if not self.state.regs.sp.concrete: 280 | print("STACK POINTER IS SYMBOLIC: " + str(self.state.regs.sp)) 281 | return 282 | for o in range(stackdepth): 283 | self.__pprint_stack_element(o) 284 | 285 | def __pprint_stack_element(self, offset): 286 | """print(stack element in the form IDX:OFFSET| ADDRESS ──> CONTENT""" 287 | l = "%s:" % ("{0:#02d}".format(offset)) 288 | l += "%s| " % ("{0:#04x}".format(offset * self.state.arch.bytes)) 289 | try: 290 | stackaddr, stackval = self.stack[offset] 291 | except IndexError: 292 | return 293 | 294 | if self.state.solver.eval(stackaddr) == self.state.solver.eval(self.state.regs.sp, cast_to=int): 295 | l += "sp" 296 | elif self.state.solver.eval(stackaddr) == self.state.solver.eval(self.state.regs.bp, cast_to=int): 297 | l += "bp" 298 | else: 299 | l += " " 300 | l += " " 301 | 302 | l += "%s " % self.cc(stackaddr) 303 | l += " ──> %s" % self.pstr_ast(stackval) 304 | print(l) 305 | 306 | def registers(self): 307 | """ 308 | Visualise the register state 309 | """ 310 | print(self.blue(headerRegs)) 311 | for reg in self.default_registers(): 312 | register_number = self.state.arch.registers[reg][0] 313 | self.__pprint_register(reg, self.state.registers.load(register_number)) 314 | 315 | def __pprint_register(self, reg, value): 316 | repr = reg.upper() + ":\t" 317 | repr += self.pstr_ast(value) 318 | print(repr) 319 | 320 | def describe_addr(self, addr): 321 | return self.__deref_addr(addr) 322 | 323 | def __deref_addr(self, addr, depth=0): 324 | if addr in self.state.memory: 325 | deref = self.state.memory.load(addr, 1) 326 | if deref.op == 'Extract': 327 | deref = deref.args[2] 328 | else: 329 | deref = self.state.mem[addr].uintptr_t.resolved 330 | if deref.concrete or not deref.uninitialized: 331 | value = self.state.solver.eval(deref) 332 | if not value == addr and not value == 0 and depth < MAX_AST_DEPTH: 333 | return " ──> %s" % self.pstr_ast(deref, depth=depth+1) 334 | 335 | def pstr_ast(self, ast, ty=None, depth=0): 336 | """Return a pretty string for an AST including a description of the derefed value if it makes sense (i.e. if 337 | the ast is concrete and the derefed value is not uninitialized 338 | More complex rendering is possible if type information is supplied 339 | """ 340 | if isinstance(ty, SimTypePointer): 341 | if ast.concrete: 342 | try: 343 | tmp = "%s ──> %s" %(self.cc(ast), self.state.mem[ast].string.concrete) 344 | except ValueError: 345 | deref = self.state.memory.load(ast, 1) 346 | if deref.op == 'Extract': 347 | return "%s ──> %s" % (self.cc(ast), self.cc(deref.args[2])) 348 | elif deref.uninitialized: 349 | return "%s ──> UNINITIALIZED" % (self.cc(ast)) 350 | else: 351 | return "%s ──> COMPLEX SYMBOLIC STRING" %(self.cc(ast)) 352 | else: 353 | return tmp 354 | else: 355 | return "%s %s" % (self.red("WARN: Symbolic Pointer"), self.cc(ast)) 356 | 357 | if ast.concrete: 358 | value = self.state.solver.eval(ast) 359 | if ast.op =='BVV' and self.__deref_addr(value, depth+1): 360 | return self.cc(ast) + self.__deref_addr(value, depth+1) 361 | else: 362 | return self.cc(ast) 363 | else: 364 | if ast.depth > MAX_AST_DEPTH: 365 | # AST is probably too large to render 366 | return self.green("" % (ast.depth, ast.variables, ast.__hash__())) 367 | return self.cc(ast) 368 | 369 | def default_registers(self): 370 | custom = { 371 | 'X86': ['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp', 'esp', 'eip'], 372 | 'AMD64': ['rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 'rbp', 'rsp', 'rip', 'r8', 'r9', 'r10', 'r11', 'r12', 373 | 'r13', 'r14', 'r15'] 374 | 375 | } 376 | if self.state.arch.name in custom: 377 | return custom[self.state.arch.name] 378 | else: 379 | l.warn("No custom register list implemented, using fallback") 380 | return self.state.arch.default_symbolic_registers \ 381 | + [self.state.arch.register_names[self.state.arch.ip_offset]] \ 382 | + [self.state.arch.register_names[self.state.arch.sp_offset]] \ 383 | + [self.state.arch.register_names[self.state.arch.bp_offset]] 384 | 385 | def pstr_branch_info(self, idx=None): 386 | """Return the information about the state concerning the last branch as a pretty string""" 387 | str_ip = self.pstr_ast(self.state.regs.ip) 388 | simplified_jump_guard = self.state.solver.simplify(self.state.history.jump_guard) 389 | str_jump_guard = self.pstr_ast(simplified_jump_guard) 390 | vars = self.state.history.jump_guard.variables 391 | 392 | return "%sIP: %s\tCond: %s\n\tVars: %s\n" % \ 393 | (str(idx) + ":\t" if type(idx) is int else "", str_ip, str_jump_guard, vars) 394 | 395 | def print_watches(self): 396 | try: 397 | self.state.watches 398 | except AttributeError: 399 | return 400 | print(self.blue(headerWatch)) 401 | 402 | for name, w in self.state.watches.eval: 403 | print("%s:\t%s" % (name, w)) 404 | return None 405 | 406 | def pstr_call_info(self, state, function): 407 | """ 408 | 409 | :param angr.SimState state: 410 | :param angr.knowledge_plugins.functions.Function function: 411 | :return: 412 | """ 413 | return [ self.pstr_call_argument(*arg) for arg in function.calling_convention.get_arg_info(state)] 414 | 415 | def pstr_call_argument(self, ty, name, location, value): 416 | """ 417 | The input should ideally be the unpacked tuple from one of the list entries of calling_convention.get_arg_info(state) 418 | :param angr.sim_type.SimType ty: 419 | :param str name: 420 | :param angr.calling_conventions.SimFunctionArgument location: 421 | :param claripy.ast.BV value: 422 | :return: 423 | """ 424 | return "%s %s@%s: %s" %( ty, name, location, self.pstr_ast(value, ty=ty)) 425 | 426 | 427 | class Stack(): 428 | def __init__(self, state): 429 | self.state = state 430 | 431 | def __getitem__(self, offset): 432 | """Returns a tuple of a stack element as (addr, content)""" 433 | addr = self.state.regs.sp + offset * self.state.arch.bytes 434 | if self.state.solver.eval(addr >= self.state.arch.initial_sp): 435 | raise IndexError 436 | return addr, self.state.memory.load(addr, size=self.state.arch.bytes, endness=self.state.arch.memory_endness) 437 | 438 | 439 | 440 | 441 | ContextView.register_default("context_view") 442 | --------------------------------------------------------------------------------