├── README.md ├── __init__.py ├── data ├── all_functions.json └── all_functions_no_fp.json ├── modules ├── __init__.py ├── annotate.py └── stacks │ ├── __init__.py │ ├── linux_x64.py │ └── linux_x86.py └── plugin.json /README.md: -------------------------------------------------------------------------------- 1 | BinaryNinja Annotator 2 | ==================== 3 | This is a plugin for Binary Ninja Reversing Platform. 4 | Upon encountering a libc function call this plugins uses virtual stack to 5 | annotate previous instructions with appropriate comment stating argument prototype. 6 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Author: carstein 3 | # Annotate function arguments 4 | 5 | from binaryninja import PluginCommand 6 | from modules import annotate 7 | 8 | 9 | # register plugin 10 | PluginCommand.register_for_function( 11 | "[Annotator] Annotate Functions", 12 | "Annotate standard libc functions with arguments", 13 | annotate.run_plugin) 14 | 15 | PluginCommand.register( 16 | "[Annotator All] Annotate Functions", 17 | "Annotate standard libc functions with arguments", 18 | annotate.run_plugin_all) 19 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carstein/Annotator/57d088128fb46381d56f56e990e09b50dff7e823/modules/__init__.py -------------------------------------------------------------------------------- /modules/annotate.py: -------------------------------------------------------------------------------- 1 | # Author: carstein 2 | # Annotate function arguments 3 | 4 | import os 5 | import json 6 | 7 | from binaryninja import * 8 | from stacks import linux_x86, linux_x64 9 | 10 | PLUGINDIR_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 11 | 12 | call_llil = [ 13 | LowLevelILOperation.LLIL_CALL, 14 | LowLevelILOperation.LLIL_CALL_STACK_ADJUST, 15 | ] 16 | 17 | # Simple database loader - assume all is in one file for now 18 | def load_database(data_path): 19 | fh = open(PLUGINDIR_PATH + '/data/' + data_path, 'r') 20 | return json.load(fh) 21 | 22 | # Function to be executed when we invoke plugin 23 | def run_plugin_all(bv): 24 | for function in bv.functions: 25 | run_plugin(bv, function) 26 | 27 | def run_plugin(bv, function): 28 | # logic of stack selection 29 | if bv.platform.name == 'linux-x86': 30 | stack = linux_x86.Stack() 31 | elif bv.platform.name == 'linux-x86_64': 32 | stack = linux_x64.Stack() 33 | else: 34 | log_error('[x] Virtual stack not found for {platform}'.format(platform=bv.platform.name)) 35 | return -1 36 | 37 | log_info('[*] Annotating function <{name}>'.format(name=function.symbol.name)) 38 | 39 | functions_db = stack.get_function_path() 40 | stack_changing_llil = stack.get_relevant_llil() 41 | 42 | db = load_database(functions_db) 43 | 44 | for block in function.low_level_il: 45 | for instruction in block: 46 | if instruction.operation in stack_changing_llil: 47 | try: 48 | stack.update(instruction) 49 | except AttributeError: 50 | log_error("[x] Attribute Error while analyzing %s." % (function.name)) 51 | 52 | if (instruction.operation in call_llil and 53 | instruction.dest.operation == LowLevelILOperation.LLIL_CONST_PTR): 54 | callee = bv.get_function_at(instruction.dest.constant) # Fetching function in question 55 | 56 | callee_name = callee.name 57 | imported = callee.symbol.type == SymbolType.ImportedFunctionSymbol 58 | db_has_key = db.has_key(callee_name) 59 | 60 | # if name isn't found, try the un- FORTIFY -ed name, if possible 61 | if not db_has_key and "_chk" in callee_name and callee_name.startswith("__") and len(callee_name) > 6: 62 | callee_name = callee_name[2:callee_name.find("_chk")] 63 | db_has_key = db.has_key(callee_name) 64 | 65 | if (imported and db_has_key): 66 | stack_args = iter(stack) 67 | 68 | for idx, function_arg in enumerate(db[callee_name]): 69 | try: 70 | stack_instruction = stack_args.next() 71 | comment = "arg{}: {}\n".format(idx+1, function_arg) 72 | function.set_comment(stack_instruction.address, comment) 73 | except StopIteration: 74 | log_error('[x] Virtual Stack Empty. Unable to find function arguments for <{}>'.format(callee.name)) 75 | -------------------------------------------------------------------------------- /modules/stacks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carstein/Annotator/57d088128fb46381d56f56e990e09b50dff7e823/modules/stacks/__init__.py -------------------------------------------------------------------------------- /modules/stacks/linux_x64.py: -------------------------------------------------------------------------------- 1 | # Virtual Call stack implementation for Linux x86 2 | 3 | # Virtual stack is represented as a dictionary 4 | # It does not store values but last instruction that modified given element 5 | # We are assuming addressing in form of ESP + X 6 | # ex: 7 | # { 8 | # 0: 9 | # 8: 10 | # 16: 11 | # } 12 | import linux_x86 13 | from binaryninja import LowLevelILOperation 14 | 15 | WORD_SIZE = 8 16 | 17 | mapping = { 18 | 'rdi':'rdi', 'edi': 'rdi', 'di':'rdi', 'dil':'rdi', 19 | 'rsi':'rsi', 'esi': 'rsi', 'si':'rsi', 'sil':'rsi', 20 | 'rdx':'rdx', 'edx': 'rdx', 'dx':'rdx', 'dl':'rdx', 21 | 'rcx':'rcx', 'ecx': 'rcx', 'cx':'rcx', 'cl':'rcx', 22 | 'r8':'r8', 'r8d': 'r8', 'r8w':'r8', 'r8b':'r8', 23 | 'r9':'r9', 'r9d': 'r9', 'r9w':'r9', 'r9b':'r9' 24 | } 25 | 26 | class Stack(linux_x86.Stack): 27 | def __init__(self): 28 | self.stack = {} 29 | 30 | self.registers = { 31 | 'rdi': None, 32 | 'rsi': None, 33 | 'rdx': None, 34 | 'rcx': None, 35 | 'r8': None, 36 | 'r9': None 37 | } 38 | 39 | self.stack_changing_llil = [ 40 | LowLevelILOperation.LLIL_STORE, 41 | LowLevelILOperation.LLIL_PUSH, 42 | LowLevelILOperation.LLIL_POP, 43 | LowLevelILOperation.LLIL_SET_REG] 44 | self.functions_path = 'all_functions_no_fp.json' 45 | 46 | def clear(self): 47 | self.stack = {} 48 | 49 | for reg in self.registers.keys(): 50 | self.registers[reg] = None 51 | 52 | def update(self, instr): 53 | if instr.operation == LowLevelILOperation.LLIL_PUSH: 54 | self.__process_push(instr) 55 | 56 | if instr.operation == LowLevelILOperation.LLIL_STORE: 57 | self.__process_store(instr) 58 | 59 | if instr.operation == LowLevelILOperation.LLIL_POP: 60 | self.__process_pop() 61 | 62 | if instr.operation == LowLevelILOperation.LLIL_SET_REG: 63 | self.__process_set_reg(instr) 64 | 65 | def __process_set_reg(self, set_i): 66 | if set_i.dest.name in mapping.keys(): 67 | self.registers[mapping[set_i.dest.name]] = set_i 68 | 69 | def __process_store(self, store_i): 70 | # Extracting destination of LLIL_STORE 71 | if store_i.dest.operation == LowLevelILOperation.LLIL_REG: 72 | dst = store_i.dest.src 73 | shift = 0 74 | else: # assuming LLIL_ADD for now 75 | dst = store_i.dest.left.src 76 | shift = store_i.dest.right.value 77 | 78 | if dst.name == 'esp': 79 | # Place it on the stack 80 | self.stack[shift] = store_i 81 | 82 | def __iter__(self): 83 | yield self.registers['rdi'] 84 | yield self.registers['rsi'] 85 | yield self.registers['rdx'] 86 | yield self.registers['rcx'] 87 | yield self.registers['r8'] 88 | yield self.registers['r9'] 89 | 90 | for index in sorted(self.stack): 91 | yield self.stack[index] 92 | -------------------------------------------------------------------------------- /modules/stacks/linux_x86.py: -------------------------------------------------------------------------------- 1 | # Virtual Call stack implementation for Linux x86 2 | 3 | # Virtual stack is represented as a dictionary 4 | # It does not store values but last instruction that modified given element 5 | # We are assuming addressing in form of ESP + X 6 | # ex: 7 | # { 8 | # 0: 9 | # 4: 10 | # 8: 11 | # } 12 | 13 | from binaryninja import LowLevelILOperation, log_info 14 | 15 | WORD_SIZE = 4 16 | 17 | class Stack: 18 | def __init__(self): 19 | self.stack = {} 20 | self.stack_changing_llil = [LowLevelILOperation.LLIL_STORE, 21 | LowLevelILOperation.LLIL_PUSH, 22 | LowLevelILOperation.LLIL_POP] 23 | self.functions_path = 'all_functions_no_fp.json' 24 | 25 | def get_function_path(self): 26 | return self.functions_path 27 | 28 | def get_relevant_llil(self): 29 | return self.stack_changing_llil 30 | 31 | def clear(self): 32 | self.stack = {} 33 | 34 | def update(self, instr): 35 | if instr.operation == LowLevelILOperation.LLIL_PUSH: 36 | self.__process_push(instr) 37 | 38 | if instr.operation == LowLevelILOperation.LLIL_STORE: 39 | self.__process_store(instr) 40 | 41 | if instr.operation == LowLevelILOperation.LLIL_POP: 42 | self.__process_pop() 43 | 44 | #self.__display_stack() 45 | 46 | def __shift_stack_right(self): 47 | for index in sorted(self.stack, reverse=True): 48 | self.stack[index+WORD_SIZE] = self.stack[index] 49 | 50 | def __shift_stack_left(self): 51 | for index in sorted(self.stack)[1:]: 52 | self.stack[index-WORD_SIZE] = self.stack[index] 53 | 54 | def __process_push(self, push_i): 55 | self.__shift_stack_right() 56 | self.stack[0] = push_i 57 | 58 | def __process_pop(self): 59 | self.__shift_stack_left() 60 | 61 | def __process_store(self, store_i): 62 | # Extracting destination of LLIL_STORE 63 | if store_i.dest.operation == LowLevelILOperation.LLIL_REG: 64 | dst = store_i.dest.src 65 | shift = 0 66 | else: # assuming LLIL_ADD for now 67 | dst = store_i.dest.left.src 68 | if store_i.dest.right.operation == LowLevelILOperation.LLIL_CONST: 69 | shift = store_i.dest.right.constant 70 | else: 71 | shift = store_i.dest.right.value 72 | 73 | if dst.name == 'esp': 74 | # Place it on the stack 75 | self.stack[shift] = store_i 76 | 77 | def __iter__(self): 78 | for index in sorted(self.stack): 79 | yield self.stack[index] 80 | 81 | def __display_stack(self): 82 | s = '\n' 83 | for index in sorted(self.stack): 84 | s += '{}: {}\n'.format(index, self.stack[index]) 85 | 86 | log_info(s) 87 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": { 3 | "name": "Annotator", 4 | "type": ["binaryview"], 5 | "api": "python2", 6 | "description": "A plugins that annotates libc function arguments.", 7 | "longdescription": "Upon encountering a libc function call this plugins uses virtual stack to annotate previous instructions with appropriate comment stating argument prototype.", 8 | "license": { 9 | "name": "MIT", 10 | "text": "Copyright (c) \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 | "dependencies": { 13 | "pip": [], 14 | "apt": [], 15 | "installers": [], 16 | "other": [] 17 | }, 18 | "version": "0.2", 19 | "author": "Michal Melewski ", 20 | "minimumBinaryNinjaVersion": { 21 | "dev": "1.0.dev-175", 22 | "release": "1.0.13" 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------