├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── binjago ├── __init__.py ├── prologues.py ├── rop.py └── stdcall.py └── plugin.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 zznop0x90@gmail.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # binjago 2 | Author: **zznop** 3 | 4 | ## Description 5 | binjago is a static analysis and exploit development framework in the form of a Binary Ninja plugin 6 | 7 | Binjago currently features plugins to: 8 | * Calculate x86 and x86-64 ROP gadgets 9 | * Identify function prologues in binary flat files (firmware) 10 | * Apply comments to standard function calls 11 | 12 | ## Minimum Version 13 | 14 | This plugin requires the following minimum version of Binary Ninja: 15 | 16 | * release - 1.1.922 17 | 18 | ## License 19 | 20 | This plugin is released under a [MIT](LICENSE) license. 21 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from binaryninja import * 2 | from binjago import * 3 | 4 | def find_func_symbol_refs(view): 5 | bt = StdCallSearch(view) 6 | bt.start() 7 | 8 | def find_rop_gadgets(view): 9 | rop_search = ROPSearch(view) 10 | rop_search.start() 11 | 12 | def find_prologues(view): 13 | sig_search = PrologSearch(view) 14 | sig_search.start() 15 | 16 | PluginCommand.register( 17 | "binjago: Find standard function references", 18 | "Locate and annotate symbol references for standard API calls", 19 | find_func_symbol_refs 20 | ) 21 | 22 | PluginCommand.register( 23 | "binjago: Find ROP gadgets", 24 | "Search .text for ROP gadgets", 25 | find_rop_gadgets 26 | ) 27 | 28 | PluginCommand.register( 29 | "binjago: Find function prologues", 30 | "Search binary files for function prologues", 31 | find_prologues 32 | ) 33 | -------------------------------------------------------------------------------- /binjago/__init__.py: -------------------------------------------------------------------------------- 1 | from .stdcall import * 2 | from .rop import * 3 | from .prologues import * 4 | -------------------------------------------------------------------------------- /binjago/prologues.py: -------------------------------------------------------------------------------- 1 | """prologues.py: identify function entry points in flat file binaries 2 | """ 3 | 4 | from binaryninja import * 5 | 6 | class PrologSearch(BackgroundTaskThread): 7 | """Class that assists in locating function prologues in flat files binaries such as firmware 8 | """ 9 | def __init__(self, view): 10 | BackgroundTaskThread.__init__(self, "", True) 11 | self.view = view 12 | self.signatures = { 13 | 'Intel x86 function prologue' : ["\x55\x89\xE5\x83\xEC", "\x55\x89\xE5\x57\x56"], 14 | 'Intel x86 NOP Instructions' : ["\x90\x90\x90\x90\x90\x90\x90\x90",], 15 | 'ARM big-endian function prologue' : ["\xe9\x2d",], 16 | 'ARM little-endian function prologue' : ["\x2d\xe9"], 17 | } 18 | self.max_sig_size = -8 19 | self.hits = {} 20 | 21 | def _search_for_func_prologues(self): 22 | """Iterate data a page at a time using BinaryReader and search for 23 | function prologue signatures 24 | """ 25 | for desc, sigs in self.signatures.iteritems(): 26 | for sig in sigs: 27 | nextaddr = 0 28 | while True: 29 | nextaddr = self.view.find_next_data(nextaddr, sig) 30 | if nextaddr == None: 31 | break 32 | 33 | self.hits[nextaddr] = desc 34 | nextaddr = nextaddr + len(sig) 35 | 36 | def _display_report(self): 37 | """Generate and display the markdown report 38 | """ 39 | md = "" 40 | for key, val in self.hits.iteritems(): 41 | md += "**{:08x}** {}\n\n".format(key, val) 42 | 43 | self.view.show_markdown_report("Function Prologue Search", md) 44 | 45 | def run(self): 46 | """Locate prologues containined in binary 47 | """ 48 | self._search_for_func_prologues() 49 | if self.hits != {}: 50 | self._display_report() 51 | else: 52 | show_message_box( 53 | "binjago: Function Prologue Search", 54 | "Could not find any function prologues" 55 | ) 56 | -------------------------------------------------------------------------------- /binjago/rop.py: -------------------------------------------------------------------------------- 1 | """rop.py: Calculates ROP gadgets contained in the executable sections 2 | of binaries 3 | """ 4 | 5 | from binaryninja import * 6 | from operator import itemgetter 7 | import binascii 8 | import argparse 9 | 10 | _PREV_BYTE_SIZE = 9 11 | 12 | _RET_INSTRS = { 13 | "retn" : ["\xc3", "\xf2\xc3"], 14 | "retf" : ["\xcb",], 15 | } 16 | 17 | def _parse_args(): 18 | """Parse command line arguments 19 | """ 20 | parser = argparse.ArgumentParser(description='Calculate ROP gadgets') 21 | parser.add_argument('--file', type=str, required=True, 22 | help = 'File path to target binary') 23 | 24 | return parser.parse_args() 25 | 26 | def _disas_all_instrs(bv, start_addr, ret_addr): 27 | """Disassemble all instructions in chunk 28 | """ 29 | global _RET_INSTRS 30 | instructions = [] 31 | curr_addr = start_addr 32 | while curr_addr < ret_addr: 33 | instr = bv.get_disassembly(curr_addr) 34 | 35 | # bad instruction 36 | if instr == None: 37 | return None 38 | 39 | # exclude jumps 40 | if instr[0] == 'j': 41 | return None 42 | 43 | # exclude leaves 44 | if instr == 'leave': 45 | return None 46 | 47 | # we don't want two rets 48 | if instr in _RET_INSTRS.keys(): 49 | return None 50 | 51 | instructions.append(instr) 52 | curr_addr += bv.get_instruction_length(curr_addr) 53 | 54 | # ret opcode was included in last instruction calculation 55 | if curr_addr != ret_addr: 56 | return None 57 | 58 | return instructions 59 | 60 | def _calculate_gadget_from_ret(bv, gadgets, baseaddr, ret_addr): 61 | """Decrement index from ret instruction and calculate gadgets 62 | """ 63 | global _PREV_BYTE_SIZE 64 | ret_instr = bv.get_disassembly(ret_addr) 65 | for i in range(1, _PREV_BYTE_SIZE): 66 | instructions = _disas_all_instrs(bv, ret_addr - i, ret_addr) 67 | if instructions == None: 68 | continue 69 | 70 | gadget_str = "" 71 | for instr in instructions: 72 | gadget_str += "{} ; ".format(instr) 73 | 74 | gadget_rva = ret_addr - i - baseaddr 75 | gadgets[gadget_rva] = "{}{}".format(gadget_str, ret_instr) 76 | 77 | return gadgets 78 | 79 | def _find_gadgets_in_data(bv, baseaddr, section): 80 | """Find ret instructions and spawn a thread to calculate gadgets 81 | for each hit 82 | """ 83 | global _RET_INSTRS 84 | gadgets = {} 85 | for ret_instr, bytecodes in _RET_INSTRS.iteritems(): 86 | for bytecode in bytecodes: 87 | next_start = section.start 88 | next_ret_addr = 0 89 | while next_start < section.end: 90 | next_ret_addr = bv.find_next_data(next_start, bytecode) 91 | if next_ret_addr == None: 92 | break 93 | 94 | # TODO: thread this 95 | gadgets = _calculate_gadget_from_ret(bv, gadgets, baseaddr, next_ret_addr) 96 | next_start = next_ret_addr + len(bytecode) 97 | 98 | return gadgets 99 | 100 | def _generate_markdown_report(bv, gadgets, title): 101 | """Display ROP gadgets 102 | """ 103 | markdown = "" 104 | found = [] 105 | for addr, gadget in sorted(gadgets.items(), key=itemgetter(1)): 106 | if gadget not in found: 107 | markdown += "**{:08x}** ```{}```\n\n".format(addr, gadget) 108 | found.append(gadget) 109 | 110 | bv.show_markdown_report(title, markdown) 111 | 112 | def _print_gadgets(gadgets): 113 | """Display ROP gadgets in headless mode 114 | """ 115 | markdown = "" 116 | found = [] 117 | for addr, gadget in sorted(gadgets.items(), key=itemgetter(1)): 118 | if gadget not in found: 119 | print "{:08x} {}".format(addr, gadget) 120 | 121 | class ROPSearch(BackgroundTaskThread): 122 | """Class that assists in locating ROP gadgets in exectable code segments 123 | """ 124 | def __init__(self, view): 125 | BackgroundTaskThread.__init__(self, "", True) 126 | self.view = view 127 | self.gadgets = {} 128 | self.progress = "binjago: Searching for ROP gadgets..." 129 | self.threads = [] 130 | self.ret_instrs = { 131 | "retn" : ["\xc3", "\xf2\xc3"], 132 | "retf" : ["\xcb",], 133 | } 134 | 135 | def run(self): 136 | """Locate ROP gadgets contain in executable sections of a binary 137 | """ 138 | if not self.view.executable: 139 | return 140 | 141 | baseaddr = self.view.segments[0].start 142 | section = self.view.get_section_by_name(".text") 143 | gadgets = _find_gadgets_in_data(self.view, baseaddr, section) 144 | 145 | if gadgets != {}: 146 | _generate_markdown_report(self.view, gadgets, "ROP Gadgets") 147 | else: 148 | show_message_box("binjago: ROP Gadget Search", "Could not find any ROP gadgets") 149 | 150 | self.progress = "" 151 | 152 | def run_headless(): 153 | """Run as headless script 154 | """ 155 | args = _parse_args() 156 | bv = BinaryViewType.get_view_of_file(args.file) 157 | bv.update_analysis_and_wait() 158 | if not bv.executable: 159 | print "! binary does not contain executable code" 160 | 161 | baseaddr = bv.segments[0].start 162 | section = bv.get_section_by_name(".text") 163 | gadgets = _find_gadgets_in_data(bv, baseaddr, section) 164 | if gadgets != {}: 165 | _print_gadgets(gadgets) 166 | 167 | if __name__ == '__main__': 168 | run_headless() 169 | -------------------------------------------------------------------------------- /binjago/stdcall.py: -------------------------------------------------------------------------------- 1 | """stdcall.py: Includes functionality for identifying symbol referneces 2 | for libc or Windows API calls 3 | """ 4 | 5 | from binaryninja import * 6 | from collections import OrderedDict 7 | 8 | class StdCallSearch(BackgroundTaskThread): 9 | """Helper class that assists in locating and applying comments where there 10 | are references to interesting standard calls 11 | """ 12 | def __init__(self, view): 13 | BackgroundTaskThread.__init__(self, "", True) 14 | self.view = view 15 | self.markdown = "" 16 | self.progress = "binjago: Searching for standard function references..." 17 | 18 | def _find_func_symbol_refs(self, symbol_name, params): 19 | """Iterate function symbol references and gather information on 20 | each function call 21 | """ 22 | symbols_to_process = [] 23 | symbols = self.view.get_symbols() 24 | for symbol in symbols: 25 | name = symbol.name.replace("@IAT", "") 26 | if len(name) < len(symbol_name): 27 | continue 28 | 29 | if name[len(symbol_name) * -1:] == symbol_name: 30 | symbols_to_process.append(symbol) 31 | 32 | if len(symbols_to_process) == 0: 33 | return 34 | 35 | md = "" 36 | for symbol in symbols_to_process: 37 | for ref in self.view.get_code_refs(symbol.address): 38 | function = ref.function 39 | addr = ref.address 40 | md_entry = "" 41 | comment = "" 42 | for name, position in params.iteritems(): 43 | md_entry += " **{}**: ```{}```\n\n".format( 44 | name, function.get_parameter_at(addr, None, position)) 45 | comment += " {}: {}\n".format( 46 | name, function.get_parameter_at(addr, None, position)) 47 | 48 | md += "### {:08x} - {}\n".format(addr, symbol.name) 49 | md += md_entry 50 | function.set_comment(ref.address, comment) 51 | 52 | if md != "": 53 | self.markdown += md 54 | 55 | def run(self): 56 | """Search for symbol references for standard function calls 57 | """ 58 | self._find_func_symbol_refs("malloc", OrderedDict([('n', 0),])) 59 | self._find_func_symbol_refs("realloc", OrderedDict([('ptr', 0), ('n', 1)])) 60 | self._find_func_symbol_refs("calloc", OrderedDict([('num', 0), ('size', 1)])) 61 | self._find_func_symbol_refs("memcpy", OrderedDict([('dst', 0), ('src', 1), ('n', 2)])) 62 | self._find_func_symbol_refs("strcpy", OrderedDict([('dst', 0), ('src', 1)])) 63 | self._find_func_symbol_refs("strncpy", OrderedDict([('dst', 0), ('src', 1), ('n', 2)])) 64 | self._find_func_symbol_refs("strlcpy", OrderedDict([('dst', 0), ('src', 1), ('n', 2)])) 65 | self._find_func_symbol_refs("strncat", OrderedDict([('dst', 0), ('src', 1), ('n', 2)])) 66 | self._find_func_symbol_refs("sprintf", OrderedDict([('dst', 0), ('src', 1), ('arg1', 2)])) 67 | self._find_func_symbol_refs("snprintf", OrderedDict([('dst', 0), ('size', 1), ('format', 2)])) 68 | self._find_func_symbol_refs("strcat", OrderedDict([('dst', 0), ('src', 1)])) 69 | self._find_func_symbol_refs("strlcat", OrderedDict([('dst', 0), ('src', 1), ('n', 2)])) 70 | self._find_func_symbol_refs("vsprintf", OrderedDict([('dst', 0), ('src', 1), ('arg_list', 2)])) 71 | self._find_func_symbol_refs("fwrite", OrderedDict([('ptr', 0), ('n', 1), ('count', 2), ('stream', 3)])) 72 | self._find_func_symbol_refs("fread", OrderedDict([('ptr', 0), ('n', 1), ('count', 2), ('stream', 3)])) 73 | self._find_func_symbol_refs("strcmp", OrderedDict([('str1', 0), ('str2', 1)])) 74 | self._find_func_symbol_refs("strncmp", OrderedDict([('str1', 0), ('str2', 1), ('num', 2)])) 75 | self._find_func_symbol_refs("fgets", OrderedDict([('str', 0), ('num', 1), ('stream', 2)])) 76 | self._find_func_symbol_refs("strlen", OrderedDict([('str', 0)])) 77 | self._find_func_symbol_refs("mprotect", OrderedDict([('addr', 0), ('len', 1), ('prot', 2)])) 78 | self._find_func_symbol_refs("mmap", OrderedDict([('addr', 0), ('len', 1), ('prot', 2), ('flags', 3), ('fd', 4), ('offset', 5)])) 79 | self._find_func_symbol_refs("munmap", OrderedDict([('addr', 0), ('len', 1)])) 80 | 81 | if self.markdown != "": 82 | self.view.show_markdown_report("Standard Function Search", self.markdown) 83 | else: 84 | show_message_box( 85 | "binjago: Standard Function Search", 86 | "Could not find any memory function symbol references" 87 | ) 88 | 89 | self.progress = "" 90 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 1, 3 | "name": "binjago", 4 | "type": ["helper" ], 5 | "api": ["python2"], 6 | "description": "x86 ROP gadget calculation, libc call annotations, and prologue signature searching", 7 | "longdescription": "Binjago is a random subset of helper plugins for annotating libc calls, calculating x86 ROP gadgets, and finding function prologue signatures in flat files", 8 | "license": { 9 | "name": "MIT", 10 | "text": "Copyright (c) 2017 zznop\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 11 | 12 | }, 13 | "platforms": ["Linux"], 14 | "dependencies": { 15 | }, 16 | "version": "1.0", 17 | "author": "zznop", 18 | "minimumbinaryninjaversion": 0 19 | } 20 | --------------------------------------------------------------------------------