├── .gitignore ├── LICENSE ├── README.md ├── plugin.json ├── __init__.py ├── hookmanager.py └── hook.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Grant Orndorff 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo is no longer maintained. Please use [this fork](https://github.com/jeffli678/bnhook) instead. Thank you [jeffli678](https://github.com/jeffli678) for adopting this plugin :) 2 | 3 | # BNHook (v1.1) 4 | Author: **Grant Orndorff** 5 | _Insert custom hooks_ 6 | ## Description: 7 | Currently supports x86 and x86_64 ELF executables. 8 | Write a hook in assembly and have it execute as part of your binary. See your hook added to the binary and reflected in the disassembly graph. The code segment of the binary is extended to make room for the hook. After insertion, Save Contents As and run the binary with your inserted code! 9 | 10 | 11 | ## Installation Instructions 12 | 13 | ### Windows 14 | 15 | pip install filebytes or pip3 install filebytes 16 | 17 | ### Linux 18 | 19 | pip install filebytes or pip3 install filebytes 20 | 21 | ### Darwin 22 | 23 | pip install filebytes or pip3 install filebytes 24 | ## Minimum Version 25 | 26 | This plugin requires the following minimum version of Binary Ninja: 27 | 28 | * 1407 29 | 30 | 31 | 32 | ## Required Dependencies 33 | 34 | The following dependencies are required for this plugin: 35 | 36 | * pip - filebytes 37 | 38 | 39 | ## License 40 | 41 | This plugin is released under a MIT license. 42 | ## Metadata Version 43 | 44 | 2 45 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "BNHook", 4 | "type": ["core", "ui" ], 5 | "api": ["python2", "python3"], 6 | "description": "Insert custom hooks", 7 | "longdescription": "Currently supports x86 and x86_64 ELF executables.\nWrite a hook in assembly and have it execute as part of your binary. See your hook added to the binary and reflected in the disassembly graph. The code segment of the binary is extended to make room for the hook. After insertion, Save Contents As and run the binary with your inserted code!", 8 | "license": { 9 | "name": "MIT", 10 | "text": "Copyright (c) 2017 Grant Orndorff\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": ["Windows", "Linux", "Darwin"], 14 | "installinstructions" : { 15 | "Windows":"pip install filebytes or pip3 install filebytes", 16 | "Linux":"pip install filebytes or pip3 install filebytes", 17 | "Darwin":"pip install filebytes or pip3 install filebytes" 18 | }, 19 | "dependencies": { 20 | "pip": ["filebytes"] 21 | }, 22 | "version": "1.1", 23 | "author": "Grant Orndorff", 24 | "minimumbinaryninjaversion": 1407 25 | } 26 | 27 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from binaryninja.plugin import PluginCommand 2 | from binaryninja.interaction import * 3 | from binaryninja.enums import MessageBoxIcon 4 | 5 | from .hookmanager import hook_manager_create 6 | from .hook import hook_create 7 | 8 | g_bn_hook_manager = None 9 | 10 | def insert_hook(bv, addr): 11 | name = TextLineField('Hook Name') 12 | ASM_FILE = 'asm file' 13 | ASM_TEXTBOX = 'asm textbox' 14 | asm_input = ChoiceField('Hook code input', [ASM_FILE, ASM_TEXTBOX]) 15 | ok = get_form_input([name, asm_input], 'Insert Hook') 16 | if not ok: 17 | show_message_box('Form Fail', 'The form returned an error.', icon=MessageBoxIcon.ErrorIcon) 18 | return False 19 | 20 | hook = hook_create(bv, addr, name.result) 21 | 22 | if asm_input.result == 0: 23 | asm_file_name = get_open_filename_input('asm file') 24 | if not asm_file_name: 25 | return False 26 | with open(asm_file_name) as asm_file: 27 | ok = hook.parse_asm_string(asm_file.read()) 28 | else: 29 | asm_string = MultilineTextField('') 30 | ok = get_form_input([asm_string], 'asm code') 31 | if not ok: 32 | return False 33 | ok = hook.parse_asm_string(asm_string.result) 34 | 35 | if not ok: 36 | return False 37 | 38 | global g_bn_hook_manager 39 | if g_bn_hook_manager is None: 40 | g_bn_hook_manager = hook_manager_create(bv) 41 | if (g_bn_hook_manager is None): 42 | show_message_box('Unimplemented', 'This plugin does not support executables of type {} yet.'.format(bv.view_type), icon=MessageBoxIcon.ErrorIcon) 43 | return False 44 | 45 | ok = g_bn_hook_manager.install_hook(hook) 46 | 47 | if not ok: 48 | show_message_box('Install Fail', 'The hook failed to install.', icon=MessageBoxIcon.ErrorIcon) 49 | return False 50 | 51 | return True 52 | 53 | 54 | PluginCommand.register_for_address('Insert Custom Hook', 'jumps to a custom piece of code, and jumps back, allowing the binary to continue as originally programmed', insert_hook) 55 | 56 | -------------------------------------------------------------------------------- /hookmanager.py: -------------------------------------------------------------------------------- 1 | from binaryninja.interaction import show_message_box 2 | from binaryninja.binaryview import BinaryReader 3 | from binaryninja.enums import MessageBoxIcon 4 | from filebytes.elf import ELF, PT, PF 5 | 6 | from .hook import Hook 7 | import sys 8 | 9 | PY2 = sys.version_info[0] == 2 10 | PY3 = sys.version_info[0] == 3 11 | PY34 = sys.version_info[0:2] >= (3, 4) 12 | 13 | 14 | if PY3: 15 | buffer=bytes 16 | 17 | 18 | class HookManager(): 19 | 20 | def __init__(self, bv): 21 | self.hooks = [] 22 | 23 | self.bv = bv 24 | self.rawbv = bv.parent_view 25 | 26 | 27 | def install_hook(self, hook): 28 | assert hook.is_assembled(), "Invalid Hookstate" 29 | 30 | self.parse_binary() 31 | 32 | code_start_addr = self.make_space(hook.code_length()) 33 | if not code_start_addr: 34 | return False 35 | 36 | install_success = hook.install(code_start_addr) 37 | if not install_success: 38 | return False 39 | 40 | return self.track_hook(hook) 41 | 42 | 43 | def track_hook(self, hook): 44 | assert hook.is_installed(), "Invalid Hookstate" 45 | self.hooks.append(hook) 46 | return True 47 | 48 | 49 | ''' Subclasses must implement these ''' 50 | def parse_binary(self): 51 | raise NotImplementedError() 52 | def make_space(self, amount): 53 | raise NotImplementedError() 54 | 55 | 56 | class ElfHookManager(HookManager): 57 | def parse_binary(self): 58 | br = BinaryReader(self.rawbv) 59 | br.seek(0) 60 | binary_bytes = br.read(self.rawbv.end) 61 | self.bininfo = ELF('thisbinary', binary_bytes) 62 | self.text_seg = None 63 | self.text_seg_index = 0 64 | for s in self.bininfo.segments: 65 | if s.header.p_type == PT.LOAD and s.header.p_flags & PF.EXEC: 66 | self.text_seg = s 67 | break 68 | self.text_seg_index += 1 69 | 70 | if self.text_seg is None: 71 | show_message_box('Parse Fail', 'Can\'t find text segment of binary!', icon=MessageBoxIcon.ErrorIcon) 72 | return False 73 | 74 | return True 75 | 76 | def make_space(self, amount): 77 | code_start_addr = self.text_seg.header.p_vaddr + self.text_seg.header.p_memsz 78 | 79 | self.bv.remove_auto_segment(self.text_seg.header.p_vaddr, self.text_seg.header.p_memsz) 80 | 81 | self.text_seg.header.p_memsz += amount 82 | self.text_seg.header.p_filesz += amount 83 | 84 | e_header = self.bininfo.elfHeader.header 85 | self.rawbv.write(e_header.e_phoff + (e_header.e_phentsize * self.text_seg_index), buffer(self.text_seg.header)[:]) 86 | 87 | self.bv.add_auto_segment(self.text_seg.header.p_vaddr, self.text_seg.header.p_memsz, self.text_seg.header.p_offset, self.text_seg.header.p_memsz, 5) 88 | 89 | return code_start_addr 90 | 91 | 92 | def hook_manager_create(bv): 93 | if bv.view_type == 'ELF': 94 | return ElfHookManager(bv) 95 | else: 96 | return None 97 | -------------------------------------------------------------------------------- /hook.py: -------------------------------------------------------------------------------- 1 | from binaryninja.interaction import show_message_box 2 | from binaryninja import enum 3 | from binaryninja.enums import MessageBoxIcon 4 | import sys 5 | 6 | 7 | class HookState(enum.IntEnum): 8 | NEW = 0 9 | ASSEMBLED = 1 10 | INSTALLED = 2 11 | 12 | class Hook(): 13 | 14 | def __init__(self, bv, hook_addr, name): 15 | self._state = HookState.NEW 16 | 17 | self.bv = bv 18 | self.arch = bv.arch 19 | 20 | self.hook_name = name 21 | 22 | self.hook_addr = hook_addr 23 | self.hook_bytes = b'' 24 | self.replaced_bytes = b'' 25 | self.code_start_addr = None 26 | self.code_bytes = b'' 27 | self.ret_addr = None 28 | self.ret_bytes = b'' 29 | 30 | self.find_bytes_to_replace() 31 | 32 | 33 | def find_bytes_to_replace(self): 34 | num_bytes_to_replace = 0 35 | curr_addr = self.hook_addr 36 | while num_bytes_to_replace < self.get_hook_len(): 37 | curr_instruction_len = self.bv.get_instruction_length(arch=self.arch, addr=curr_addr) 38 | num_bytes_to_replace += curr_instruction_len 39 | curr_addr += curr_instruction_len 40 | 41 | self.ret_addr = curr_addr 42 | self.replaced_bytes = self.bv.read(self.hook_addr, num_bytes_to_replace) 43 | 44 | def is_new(self): 45 | return self._state is HookState.NEW 46 | def is_assembled(self): 47 | return self._state is HookState.ASSEMBLED 48 | def is_installed(self): 49 | return self._state is HookState.INSTALLED 50 | 51 | 52 | def parse_asm_string(self, asm_string): 53 | assert self.is_new(), "Invalid Hookstate" 54 | 55 | try: 56 | asm_bytes = self.arch.assemble(asm_string) 57 | except: 58 | err = sys.exc_info()[0] 59 | show_message_box('Assemble fail', 'Assembly of string failed:\n\n{}\n\nError: {}\n'.format(asm_string, err), icon=MessageBoxIcon.ErrorIcon) 60 | return False 61 | 62 | self.code_bytes += asm_bytes 63 | 64 | self._state = HookState.ASSEMBLED 65 | return True 66 | 67 | 68 | def code_length(self): 69 | assert self.is_assembled(), "Invalid Hookstate" 70 | return len(self.code_bytes) + len(self.replaced_bytes) + self.get_hook_len() 71 | 72 | 73 | def install(self, code_start_addr): 74 | assert self.is_assembled(), "Invalid Hookstate" 75 | 76 | self.code_start_addr = code_start_addr 77 | hook_str = self.get_hook_format().format(self.code_start_addr - self.hook_addr) 78 | try: 79 | self.hook_bytes = self.arch.assemble(hook_str) 80 | except: 81 | err = sys.exc_info()[0] 82 | show_message_box('Assemble fail', 'Assembly of string failed:\n\n{}\n\nError: {}\n'.format(hook_str, err), icon=MessageBoxIcon.ErrorIcon) 83 | return False 84 | 85 | ret_str = self.get_hook_format().format(self.ret_addr - (self.code_start_addr + self.code_length()) + self.get_hook_len()) 86 | try: 87 | self.ret_bytes = self.arch.assemble(ret_str) 88 | except: 89 | err = sys.exc_info()[0] 90 | show_message_box('Assemble fail', 'Assembly of string failed:\n\n{}\n\nError: {}\n'.format(ret_str, err), icon=MessageBoxIcon.ErrorIcon) 91 | return False 92 | 93 | written = self.bv.write(self.hook_addr, self.arch.convert_to_nop(self.replaced_bytes, 0)) 94 | if written != len(self.replaced_bytes): 95 | return False 96 | 97 | written = self.bv.write(self.hook_addr, self.hook_bytes) 98 | if written != len(self.hook_bytes): 99 | return False 100 | 101 | written = self.bv.write(self.code_start_addr, self.code_bytes + self.replaced_bytes + self.ret_bytes) 102 | if written != self.code_length(): 103 | return False 104 | 105 | self._state = HookState.INSTALLED 106 | return True 107 | 108 | 109 | ''' Subclasses must implement these ''' 110 | def get_hook_format(self): 111 | raise NotImplementedError() 112 | def get_hook_len(self): 113 | raise NotImplementedError() 114 | 115 | 116 | 117 | 118 | class x86Hook(Hook): 119 | def get_hook_format(self): 120 | return 'jmp {:#010x}' 121 | def get_hook_len(self): 122 | return 5 123 | 124 | 125 | def hook_create(bv, hook_addr, name): 126 | if bv.arch.name == 'x86' or bv.arch.name == 'x86_64': 127 | return x86Hook(bv, hook_addr, name) 128 | else: 129 | return None 130 | --------------------------------------------------------------------------------