├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── frida_intercept.py ├── frida_plugin.py ├── frida_plugin_attach.py ├── frida_plugin_intercept.py ├── frida_plugin_modify.py ├── frida_plugin_reload.py ├── frida_plugin_remove.py ├── frida_plugin_start.py ├── frida_plugin_start_attach.py ├── frida_plugin_stop.py ├── helpers.py └── plugin.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .vs/ 104 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Chame1eon 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 | # Frida Plugin (v1.0 alpha) 2 | Author: **Chame1eon** 3 | 4 | _A plugin to integrate the Frida dynamic instrumentation toolkit into Binary Ninja._ 5 | 6 | ## Description: 7 | 8 | This plugin makes use of the Frida dynamic instrumentation framework to simplify dynamic analysis within Binary Ninja. The plugin uses function definitions and type information, either identified by Binary Ninja or user inputted, to define Frida native functions automatically. To intercept a function, all a user needs to do is select the intercept button from the option menu. Once a function is being intercepted, whenever that function is called, by default, the arguments and return value will be logged to the Binary Ninja log. The behaviour of the Frida hooks can also be modified by a user. A demonstration video of the plugin in use in the following video: 9 | 10 | [Binary Ninja - Frida Plugin](http://sendvid.com/vw7froy5) 11 | 12 | 13 | ### Use Guide 14 | 15 | #### Start Plugin 16 | 17 | After installing the plugin, from either the tools menu or by right clicking on the binary view window, you can select the option "Frida: Start Plugin". Selecting this option will bring up a window asking you to select the device you want the plugin to use, any device supported by Frida should also be supported by this plugin. For example, Android and iOS devices should also appear listed here if they are plugged in via USB. 18 | 19 | #### Attach to Process 20 | 21 | Once you have the process you want to analyse running, you can select the option "Frida: Attach to Process". Choosing this option will provide you with a list of currently running processes on the system you are targeting. Selecting one of those processes will trigger the plugin to use Frida to attach to that process. 22 | 23 | #### Start Process 24 | 25 | Use this option to spawn a new process and immediately attach Frida to it. Select the option "Frida: Start and Attach Process". 26 | 27 | #### Select Module (Optional) 28 | 29 | By default, this plugin will use the name of the binary you are analysing to select the target module. For example, if you currently have libssl.so loaded into Binary Ninja, then the plugin will look for that module in the process address. However, if the binary name cannot be found in the process' address space, then the module must be selected manually by running the "Frida: Select Target Module" menu option. 30 | 31 | #### Intercept Function 32 | 33 | Now that the plugin is running, you can start intercepting functions within the binary. To intercept a function all you need to do is right click within that function and select "Frida: Intercept Function". Providing there were no errors, that function will now be intercepted by Frida and any time that function is called a log message will be printed with the argument values and return value. 34 | 35 | #### Modify Intercept 36 | 37 | To change the default behaviour for an intercepted function, you can use the option "Frida: Modify Intercept". Opening this window opens two Multiline input fields. Inside those fields you can enter JavaScript to be executed before and after the function has been run. Above each of the fields is a label to show what the existing hook looks like. 38 | 39 | #### Remove Intercept 40 | 41 | Using "Frida: Remove Intercept" will safely remove the intercept from the Frida agent. 42 | 43 | #### Frida: Reload 44 | 45 | When modifying function information in Binary Ninja, such as parameter types, there is currently no way to be notified of these events. Therefore, to update the Frida intercepts, in these cases, you will need to manually call "Frida: Reload". 46 | 47 | #### Frida: Stop Plugin 48 | 49 | Safely removes all the hooks from the attached process, before disconnecting from the process. 50 | 51 | 52 | ### Future: 53 | * Allow instruction level interception 54 | * Add support for using the Frida Stalker 55 | * Support process patching using Frida 56 | * Frida Spawn 57 | 58 | ### Warnings: 59 | * The Binary Ninja interaction API is, currently, does not support injecting text into a Multiline Field. As a result, hook modification requires a user to retype what they had previously. 60 | 61 | ## Minimum Version 62 | 63 | This plugin requires the following minimum version of Binary Ninja: 64 | 65 | * release - 9999 66 | * dev - 1.0.dev-576 67 | 68 | 69 | ## Required Dependencies 70 | 71 | The following dependencies are required for this plugin: 72 | 73 | * pip - frida 74 | * installers - 75 | * other - 76 | * apt - 77 | 78 | 79 | ## License 80 | 81 | This plugin is released under a [MIT](LICENSE) license. 82 | 83 | 84 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from collections import OrderedDict 4 | 5 | from binaryninja import * 6 | 7 | from .frida_plugin_start import FridaPluginStart 8 | from .frida_plugin_attach import FridaPluginAttach 9 | from .frida_plugin_start_attach import FridaPluginStartAttach 10 | from .frida_plugin_stop import FridaPluginStop 11 | from .frida_plugin_intercept import FridaPluginIntercept 12 | from .frida_plugin_modify import FridaPluginModify 13 | from .frida_plugin_remove import FridaPluginRemove 14 | from .frida_plugin_reload import FridaPluginReload 15 | 16 | intercepts = {} 17 | settings = Settings("binaryninja-frida") 18 | settings.register_group("frida", "Frida Settings") 19 | settings.register_setting("frida.device_id", '{"description" : "Currently selected device id.", "title" : "Frida Device ID", "default" : "", "type" : "string"}') 20 | settings.register_setting("frida.process_name", '{"description" : "Currently selected process name", "title" : "Frida Selected Process Name", "default" : "", "type" : "string"}') 21 | 22 | 23 | plugin_commands = [ 24 | { 25 | "title": "Frida\\Start Plugin", 26 | "desc": "", 27 | "plugin_module": FridaPluginStart(settings), 28 | "type": "main" 29 | }, 30 | { 31 | "title": "Frida\\Process\\Attach", 32 | "desc": "", 33 | "plugin_module": FridaPluginAttach(settings), 34 | "type": "main" 35 | }, 36 | { 37 | "title": "Frida\\Process\\Start", 38 | "desc": "", 39 | "plugin_module": FridaPluginStartAttach(settings), 40 | "type": "main" 41 | }, 42 | { 43 | "title": "Frida\\Stop Plugin", 44 | "desc": "", 45 | "plugin_module": FridaPluginStop(settings), 46 | "type": "main" 47 | }, 48 | { 49 | "title": "Frida\\Intercept\\Function", 50 | "desc": "", 51 | "plugin_module": FridaPluginIntercept(settings), 52 | "type": "function" 53 | }, 54 | { 55 | "title": "Frida\\Intercept\\Remove Function", 56 | "desc": "", 57 | "plugin_module": FridaPluginRemove(settings), 58 | "type": "function" 59 | }, 60 | { 61 | "title": "Frida\\Intercept\\Modify", 62 | "desc": "", 63 | "plugin_module": FridaPluginModify(settings), 64 | "type": "function" 65 | }, 66 | { 67 | "title": "Frida\\Reload", 68 | "desc": "", 69 | "plugin_module": FridaPluginReload(settings), 70 | "type": "main" 71 | } 72 | ] 73 | 74 | for menu_item in plugin_commands: 75 | title = menu_item["title"] 76 | desc = menu_item["desc"] 77 | plugin_module = menu_item["plugin_module"] 78 | plugin_type = menu_item["type"] 79 | 80 | if plugin_type == "main": 81 | PluginCommand.register(title, desc, plugin_module._run, plugin_module._is_valid) 82 | elif plugin_type == "function": 83 | PluginCommand.register_for_function(title, desc, plugin_module._run, plugin_module._is_valid) -------------------------------------------------------------------------------- /frida_intercept.py: -------------------------------------------------------------------------------- 1 | from binaryninja import * 2 | 3 | import base64 4 | from .helpers import to_str, to_bytes 5 | import traceback 6 | 7 | 8 | class FridaIntercept(object): 9 | def __init__(self, addr, params, ret, module_name=None, abi='default'): 10 | self.addr = addr 11 | self.params = params 12 | self.ret = ret 13 | self.module_name = module_name 14 | self.abi = FridaIntercept.to_frida_abi(abi); 15 | self.script = None 16 | self.is_running = False 17 | self.is_enabled = False 18 | self.invalidated = False 19 | 20 | self.reset_on_enter() 21 | self.reset_on_leave() 22 | 23 | def _on_message(self, message, data): 24 | log.log_info("Frida Plugin: message received from function at address " + self.addr + ":") 25 | 26 | if message["type"] == "send": 27 | log.log_info(message["payload"]) 28 | if data: 29 | log.log_info(data) 30 | elif message["type"] == "error": 31 | log.log_error(f'Error description: {message}') 32 | traceback.print_stack(limit=100) 33 | 34 | def set_on_enter(self, onEnter): 35 | self.onEnter = onEnter 36 | log.log_info(f'Debug on_enter: {onEnter}') 37 | self.invalidated = True 38 | 39 | def reset_on_enter(self): 40 | on_enter = '' 41 | for i in range(0, len(self.params)): 42 | on_enter += 'console.log("args[%d]:", args[%d]);\n' % (i, i) 43 | self.onEnter = on_enter 44 | self.invalidated = True 45 | 46 | def set_on_leave(self, onLeave): 47 | self.onLeave = onLeave 48 | self.invalidated = True 49 | 50 | def reset_on_leave(self): 51 | if self.ret != "void": 52 | self.onLeave = 'console.log("retval:", retval);' 53 | else: 54 | self.onLeave = '' 55 | self.invalidated = True 56 | 57 | def set_module_name(self, module_name): 58 | self.module_name = module_name 59 | self.invalidated = True 60 | 61 | def update_function_def(self, function): 62 | params = [] 63 | for p in function.parameter_vars: 64 | params.append(FridaIntercept.to_frida_type(p.type)) 65 | if params != self.params: 66 | self.params = params 67 | self.invalidated = True 68 | updated_ret = FridaIntercept.to_frida_type(function.return_type) 69 | if updated_ret != self.ret: 70 | self.ret = updated_ret 71 | self.invalidated = True 72 | 73 | def to_frida_script(self): 74 | script = "" 75 | if self.module_name: 76 | script = f''' 77 | var searchModule = "{self.module_name}"; 78 | var modules = Process.enumerateModules(); 79 | var moduleName = null; 80 | 81 | for (var i = 0; i < modules.length; i++) {{ 82 | if (modules[i].name.indexOf(searchModule) > -1) {{ 83 | moduleName = modules[i].name; 84 | }} 85 | }} 86 | 87 | var base = Module.findBaseAddress(moduleName); 88 | 89 | console.log("moduleName: " + moduleName); 90 | console.log("Base value: " + base); 91 | console.log("Nativefunc ptr: " + base.add(ptr("{self.addr}"))); 92 | \n''' 93 | else: 94 | script += 'var base = ptr("0x0");\n' 95 | 96 | script += f'var f = new NativeFunction(base.add(ptr("{self.addr}")), "{self.ret}", [' 97 | 98 | for p in self.params: 99 | script += '"' + p + '", ' 100 | 101 | if len(self.params) > 0: 102 | script = script[:-2] 103 | 104 | script += ']);\n' 105 | 106 | script += 'Interceptor.attach(f, {\n' 107 | 108 | if self.onEnter: 109 | script += 'onEnter: function(args) {\n' 110 | script += self.onEnter + '\n' 111 | script += '}' 112 | 113 | if self.onEnter and self.onLeave: 114 | script += ',\n' 115 | 116 | if self.onLeave: 117 | script += 'onLeave: function(retval) {\n' 118 | script += self.onLeave + '\n' 119 | script += '}\n' 120 | 121 | script += '});\n' 122 | 123 | log.log_debug(f'to_frida_script: {script}') 124 | return script 125 | 126 | def enable(self): 127 | self.is_enabled = True 128 | 129 | def start(self, script): 130 | if self.is_enabled and not self.is_running: 131 | self.script = script 132 | self.script.on('message', self._on_message); 133 | self.script.load() 134 | self.is_running = True 135 | self.invalidated = False 136 | 137 | def stop(self): 138 | if self.is_running: 139 | if self.script != None: 140 | self.script.unload() 141 | self.is_running = False 142 | 143 | def disable(self): 144 | self.is_enabled = False 145 | self.stop() 146 | 147 | def is_invalidated(self): 148 | return self.invalidated 149 | 150 | def reload(self, script): 151 | self.stop() 152 | self.start(script) 153 | 154 | def serialize(self): 155 | intercept = {} 156 | 157 | intercept["addr"] = self.addr 158 | intercept["params"] = self.params 159 | intercept["ret"] = self.ret 160 | intercept["abi"] = self.abi 161 | intercept["module_name"] = self.module_name 162 | intercept["on_enter"] = to_str(base64.b64encode(to_bytes(self.onEnter))) 163 | intercept["on_leave"] = to_str(base64.b64encode(to_bytes(self.onLeave))) 164 | intercept["is_enabled"] = self.is_enabled 165 | 166 | return intercept 167 | 168 | @staticmethod 169 | def deserialize(intercept): 170 | fi = FridaIntercept(intercept["addr"], intercept["params"], intercept["ret"], intercept["module_name"], intercept["abi"]) 171 | 172 | fi.onEnter = to_str(base64.b64decode(to_bytes(intercept["on_enter"]))) 173 | fi.onLeave = to_str(base64.b64decode(to_bytes(intercept["on_leave"]))) 174 | fi.is_enabled = intercept["is_enabled"] 175 | 176 | return fi 177 | 178 | @staticmethod 179 | def from_bn_function(function, base, module_name=None): 180 | addr = "0x%x" % (function.start - base) 181 | params = [] 182 | for p in function.parameter_vars: 183 | params.append(FridaIntercept.to_frida_type(p.type)) 184 | ret = FridaIntercept.to_frida_type(function.return_type) 185 | actual_addr = addr 186 | if "thumb" in function.platform.name: 187 | actual_addr = "0x%x" % ((function.start - base) + 1) 188 | return FridaIntercept(actual_addr, params, ret, module_name, function.calling_convention.name) 189 | 190 | @staticmethod 191 | def to_frida_type(bn_type): 192 | t = "" 193 | 194 | if bn_type.type_class == TypeClass.IntegerTypeClass: 195 | t += "int" + str(bn_type.width * 8) 196 | if not bn_type.signed: 197 | t = "u" + t 198 | elif bn_type.type_class == TypeClass.FloatTypeClass: 199 | if bn_type.width == 4: 200 | t = "float" 201 | elif bn_type_width == 8: 202 | t = "double" 203 | elif bn_type.type_class == TypeClass.PointerTypeClass: 204 | t = "pointer" 205 | elif bn_type.type_class == TypeClass.VoidTypeClass: 206 | t = "void" 207 | 208 | return t 209 | 210 | @staticmethod 211 | def to_frida_abi(bn_abi): 212 | abi = bn_abi 213 | 214 | if abi == "cdecl": 215 | abi = "mscdecl" 216 | 217 | return abi 218 | -------------------------------------------------------------------------------- /frida_plugin.py: -------------------------------------------------------------------------------- 1 | from binaryninja import * 2 | 3 | import frida 4 | import os 5 | import json 6 | 7 | from .frida_intercept import FridaIntercept 8 | 9 | class FridaPlugin(object): 10 | def __init__(self, settings): 11 | self.settings = settings 12 | self.frida_device = None 13 | self.frida_session = None 14 | self.module_name = None 15 | self.intercepts = None 16 | self.global_bv = None 17 | 18 | def _load_metadata(self, bv): 19 | try: 20 | self.intercepts = {} 21 | serialized_intercepts = json.loads(bv.query_metadata("frida_plugin_intercepts")) 22 | 23 | for addr, intercept in list(serialized_intercepts.items()): 24 | if addr not in self.intercepts: 25 | self.intercepts[addr] = FridaIntercept.deserialize(intercept) 26 | 27 | if self.intercepts[addr].is_enabled: 28 | function = bv.get_functions_containing(int(addr, 16))[0] 29 | function.set_auto_instr_highlight(int(addr, 16), HighlightColor(red=0xEF, blue=0x64, green=0x56)) 30 | except KeyError: 31 | pass 32 | 33 | def _store_metadata(self, bv): 34 | serialized_intercepts = {} 35 | for addr, intercept in list(self.intercepts.items()): 36 | serialized_intercepts[addr] = intercept.serialize() 37 | 38 | bv.store_metadata("frida_plugin_intercepts", json.dumps(serialized_intercepts)) 39 | 40 | def _check_and_load_metadata(self, bv): 41 | if "intercepts" not in bv.session_data: 42 | self._load_metadata(bv) 43 | 44 | def _load_session_data(self, bv): 45 | if "intercepts" in bv.session_data: 46 | self.intercepts = bv.session_data["intercepts"] 47 | if "frida_device" in bv.session_data: 48 | self.frida_device = bv.session_data["frida_device"] 49 | if "frida_session" in bv.session_data: 50 | self.frida_session = bv.session_data["frida_session"] 51 | if "module_name" in bv.session_data: 52 | self.module_name = bv.session_data["module_name"] 53 | 54 | def _store_session_data(self, bv): 55 | bv.session_data["intercepts"] = self.intercepts 56 | bv.session_data["frida_device"] = self.frida_device 57 | bv.session_data["frida_session"] = self.frida_session 58 | bv.session_data["module_name"] = self.module_name 59 | 60 | def _reload_intercepts(self): 61 | for addr, intercept in list(self.intercepts.items()): 62 | intercept.update_function_def(self.global_bv.get_functions_containing(int(addr, 16))[0]) 63 | if intercept.is_running and intercept.is_invalidated(): 64 | intercept.reload(self.frida_session.create_script(intercept.to_frida_script())) 65 | 66 | def _build_or_get_intercept(self, bv, addr, function): 67 | if addr not in self.intercepts: 68 | self.intercepts[addr] = FridaIntercept.from_bn_function(function, bv.start, self.module_name) 69 | return self.intercepts[addr] 70 | else: 71 | return self.intercepts[addr] 72 | 73 | def _run(self, bv, function=None): 74 | self._load_session_data(bv) 75 | self._check_and_load_metadata(bv) 76 | self.global_bv = bv 77 | self.run(bv, function) 78 | self._reload_intercepts() 79 | self._store_metadata(bv) 80 | self._store_session_data(bv) 81 | 82 | def run(self, bv, function=None): 83 | raise RuntimeException("Error: Frida Plugin Module must implement a run method.") 84 | 85 | def _is_valid(self, bv, function=None): 86 | self._load_session_data(bv) 87 | return self.is_valid(bv, function) 88 | 89 | def is_valid(self, bv, function=None): 90 | return True 91 | -------------------------------------------------------------------------------- /frida_plugin_attach.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | from binaryninja import * 4 | import os 5 | import frida 6 | 7 | class FridaPluginAttach(FridaPlugin): 8 | def __init__(self, settings): 9 | super(FridaPluginAttach, self).__init__(settings) 10 | 11 | def is_valid(self, bv, function=None): 12 | return self.frida_device != None 13 | 14 | def run(self, bv, function=None): 15 | device_id = self.settings.get_string("frida.device_id") 16 | if device_id: 17 | device = None 18 | try: 19 | device = frida.get_device(device_id, timeout=3) 20 | except frida.TimedOutError: 21 | log.log_error("Frida Plugin: Failed to find device. Please try reconnecting device or select another from the device menu.") 22 | return 23 | 24 | frida_processes = device.enumerate_processes() 25 | try: 26 | last_process = bv.query_metadata("frida_plugin_process_name") 27 | except KeyError: 28 | last_process = self.settings.get_string("frida.process_name") 29 | 30 | processes = [] 31 | processes_reorder = [] 32 | for process in frida_processes: 33 | if process.name == last_process: 34 | processes.insert(0, f'{process.name}-{process.pid}') 35 | processes_reorder.insert(0, process) 36 | else: 37 | processes.append(f'{process.name}-{process.pid}') 38 | processes_reorder.append(process) 39 | choice_f = ChoiceField("Processes", processes) 40 | get_form_input([choice_f], "Attach Frida to Process") 41 | if choice_f.result != None: 42 | self.settings.set_string("frida.process_name", processes_reorder[choice_f.result].name) 43 | bv.store_metadata("frida_plugin_process_name", str(processes_reorder[choice_f.result].name)) 44 | process = processes_reorder[choice_f.result] 45 | self.frida_session = device.attach(process.pid) 46 | log.log_info("Frida Plugin: Successfully connected to device.") 47 | filename = os.path.split(bv.file.filename)[-1].split('.')[0] + '.' 48 | self.module_name = filename 49 | 50 | for addr, intercept in list(self.intercepts.items()): 51 | if intercept.is_enabled: 52 | intercept.set_module_name(self.module_name) 53 | intercept.start(self.frida_session.create_script(intercept.to_frida_script())) 54 | else: 55 | log.log_error("Frida Plugin: No device set. Please select from tools menu.") 56 | -------------------------------------------------------------------------------- /frida_plugin_intercept.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | from binaryninja import * 4 | 5 | class FridaPluginIntercept(FridaPlugin): 6 | def __init__(self, settings): 7 | super(FridaPluginIntercept, self).__init__(settings) 8 | 9 | def is_valid(self, bv, function=None): 10 | addr = "0x%x" % function.start 11 | if self.intercepts != None: 12 | if addr not in self.intercepts or not self.intercepts[addr].is_enabled: 13 | return True 14 | return False 15 | 16 | def run(self, bv, function=None): 17 | addr = "0x%x" % function.start 18 | fi = self._build_or_get_intercept(bv, addr, function) 19 | fi.set_module_name(self.module_name) 20 | 21 | log.log_info("Frida Plugin: Intercepting " + function.name + " at %s" % addr) 22 | 23 | fi.enable() 24 | if self.frida_session: 25 | fi.start(self.frida_session.create_script(fi.to_frida_script())) 26 | function.set_auto_instr_highlight(function.start, HighlightColor(red=0xEF, blue=0x64, green=0x56)) -------------------------------------------------------------------------------- /frida_plugin_modify.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | from binaryninja import * 4 | 5 | class FridaPluginModify(FridaPlugin): 6 | def __init__(self, settings): 7 | super(FridaPluginModify, self).__init__(settings) 8 | 9 | def run(self, bv, function=None): 10 | addr = "0x%x" % function.start 11 | fi = self._build_or_get_intercept(bv, addr, function) 12 | 13 | on_enter_label_f = LabelField("Existing On Enter") 14 | on_enter_f = MultilineTextField("On Enter") 15 | on_leave_label_f = LabelField("Existing On Leave") 16 | on_leave_f = MultilineTextField("On Leave") 17 | get_form_input([on_enter_label_f, fi.onEnter.strip(), on_enter_f, on_leave_label_f, fi.onLeave.strip(), on_leave_f], "Frida Intercept Code") 18 | if on_enter_f.result != None and on_enter_f.result != None: 19 | if on_enter_f.result == "" and on_leave_f.result == "": 20 | fi.reset_on_enter() 21 | fi.reset_on_leave() 22 | else: 23 | fi.set_on_enter(on_enter_f.result) 24 | fi.set_on_leave(on_leave_f.result) -------------------------------------------------------------------------------- /frida_plugin_reload.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | class FridaPluginReload(FridaPlugin): 4 | def __init__(self, settings): 5 | super(FridaPluginReload, self).__init__(settings) 6 | 7 | def run(self, bv, function=None): 8 | self._reload_intercepts() -------------------------------------------------------------------------------- /frida_plugin_remove.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | from binaryninja import * 4 | 5 | class FridaPluginRemove(FridaPlugin): 6 | def __init__(self, settings): 7 | super(FridaPluginRemove, self).__init__(settings) 8 | 9 | def is_valid(self, bv, function=None): 10 | addr = "0x%x" % function.start 11 | if self.intercepts: 12 | if addr in self.intercepts: 13 | if self.intercepts[addr].is_enabled: 14 | return True 15 | return False 16 | 17 | def run(self, bv, function=None): 18 | addr = "0x%x" % function.start 19 | function.set_auto_instr_highlight(function.start, HighlightStandardColor.NoHighlightColor) 20 | log.log_info("Frida Plugin: Stopping intercept at %s" % addr) 21 | self.intercepts[addr].disable() -------------------------------------------------------------------------------- /frida_plugin_start.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | from binaryninja import * 4 | import frida 5 | 6 | class FridaPluginStart(FridaPlugin): 7 | def __init__(self, settings): 8 | super(FridaPluginStart, self).__init__(settings) 9 | 10 | def run(self, bv, function=None): 11 | frida_devices = frida.enumerate_devices() 12 | try: 13 | last_device = bv.query_metadata("frida_plugin_device_id") 14 | except KeyError: 15 | last_device = self.settings.get_string("frida.device_id") 16 | 17 | devices = [] 18 | device_reorder = [] 19 | for device in frida_devices: 20 | if device.id == last_device: 21 | devices.insert(0, device.name) 22 | device_reorder.insert(0, device) 23 | else: 24 | devices.append(device.name) 25 | device_reorder.append(device) 26 | choice_f = ChoiceField("Devices", devices) 27 | get_form_input([choice_f], "Get Frida Device") 28 | if choice_f.result != None: 29 | self.settings.set_string("frida.device_id", device_reorder[choice_f.result].id) 30 | bv.store_metadata("frida_plugin_device_id", str(device_reorder[choice_f.result].id)) 31 | self.frida_device = device_reorder[choice_f.result] -------------------------------------------------------------------------------- /frida_plugin_start_attach.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Tested on Python 3.8, Linux, Binary Ninja 2.0.2170, Frida 12.9.4 3 | ''' 4 | from .frida_plugin import FridaPlugin 5 | 6 | from binaryninja import * 7 | import os 8 | import frida 9 | 10 | 11 | class FridaPluginStartAttach(FridaPlugin): 12 | '''Purpose is to allow user to specify a binary file to spawn as a new 13 | process and have frida attach to it. 14 | ''' 15 | def __init__(self, settings): 16 | super(FridaPluginStartAttach, self).__init__(settings) 17 | self.binary_name = None 18 | 19 | def is_valid(self, bv, function=None): 20 | return self.frida_device != None 21 | 22 | def run(self, bv, function=None): 23 | device_id = self.settings.get_string("frida.device_id") 24 | if device_id: 25 | device = None 26 | try: 27 | device = frida.get_device(device_id, timeout=3) 28 | except frida.TimedOutError: 29 | log.log_error("Frida Plugin: Failed to find device. Please try reconnecting device or select another from the device menu.") 30 | return 31 | 32 | # Generate ui for reading binary name to spawn 33 | cmd_line = TextLineField('Command line') 34 | choice_field = ChoiceField("Start Process", [bv.file.original_filename, "Command line"]) 35 | ret = get_form_input([choice_field,cmd_line], "Start Process") 36 | 37 | if not ret: 38 | log.log_info("No binary to spawn specified.") 39 | return 40 | 41 | if choice_field.result == 1: 42 | # Select choice is to read textlinefiled 43 | print(choice_field.result) 44 | self.binary_name = cmd_line.result 45 | log.log_info(f"Binary is {self.binary_name}.") 46 | else: 47 | self.binary_name = bv.file.original_filename 48 | 49 | # Begin running process 50 | frida_pid = device.spawn(self.binary_name) 51 | log.log_info(f'Spawned process pid: {frida_pid}') 52 | self.frida_session = device.attach(frida_pid) 53 | 54 | self.settings.set_string("frida.process_name", self.binary_name) 55 | bv.store_metadata("frida_plugin_process_name", self.binary_name) 56 | log.log_info(f'{self.binary_name}') 57 | log.log_info("Frida Plugin: Successfully connected to device.") 58 | filename = os.path.split(bv.file.filename)[-1].split('.')[0] 59 | self.module_name = filename 60 | 61 | log.log_info(f'Applying intercepts {filename}') 62 | 63 | for addr, intercept in list(self.intercepts.items()): 64 | log.log_debug(f'Intercept debug: {intercept.to_frida_script()}') 65 | if intercept.is_enabled: 66 | intercept.set_module_name(self.module_name) 67 | intercept.start(self.frida_session.create_script(intercept.to_frida_script())) 68 | 69 | device.resume(frida_pid) 70 | 71 | else: 72 | log.log_error("Frida Plugin: No device set. Please select from tools menu.") 73 | -------------------------------------------------------------------------------- /frida_plugin_stop.py: -------------------------------------------------------------------------------- 1 | from .frida_plugin import FridaPlugin 2 | 3 | class FridaPluginStop(FridaPlugin): 4 | def __init__(self, settings): 5 | super(FridaPluginStop, self).__init__(settings) 6 | 7 | def run(self, bv, function=None): 8 | for addr, intercept in list(self.intercepts.items()): 9 | if intercept.is_enabled == True: 10 | log.log_info("Frida Plugin: Removing intercept at %s" % intercept.addr) 11 | intercept.stop() 12 | intercept.is_enabled = False 13 | 14 | if self.frida_session: 15 | self.frida_session.detach() 16 | self.frida_session = None -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Some python helper functions. 3 | Tested on Python 3.8 4 | ''' 5 | 6 | # https://learning.oreilly.com/library/view/effective-python-90/9780134854717/ch01.xhtml#ch1 7 | def to_str(bytes_or_str): 8 | if isinstance(bytes_or_str, bytes): 9 | value = bytes_or_str.decode('utf-8') 10 | else: 11 | value = bytes_or_str 12 | return value # Instance of str 13 | 14 | def to_bytes(bytes_or_str): 15 | if isinstance(bytes_or_str, str): 16 | value = bytes_or_str.encode('utf-8') 17 | else: 18 | value = bytes_or_str 19 | return value # Instance of bytes 20 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frida", 3 | "type": ["helper"], 4 | "api": ["python3"], 5 | "pluginmetadataversion": 2, 6 | "platforms": ["Linux", "Windows", "Darwin"], 7 | "installinstructions" : { 8 | "Darwin" : "Install the following pip packages: frida", 9 | "Linux" : "Install the following pip packages: frida", 10 | "Windows" : "Install the following pip packages: frida" 11 | }, 12 | "description": "A plugin to integrate the Frida dynamic instrumentation toolkit into Binary Ninja.", 13 | "longdescription": "This plugin makes use of the Frida dynamic instrumentation framework to simplify dynamic analysis within Binary Ninja. The plugin uses function definitions and type information, either identified by Binary Ninja or user inputted, to define Frida native functions automatically. To intercept a function, all a user needs to do is select the intercept button from the option menu. Once a function is being intercepted, whenever that function is called, by default, the arguments and return value will be logged to the Binary Ninja log. The behaviour of the Frida hooks can also be modified by a user. A demonstration video of the plugin in use in the following video:\n\n[Binary Ninja - Frida Plugin](http://sendvid.com/vw7froy5)\n\n\n### Use Guide\n\n#### Start Plugin\n\nAfter installing the plugin, from either the tools menu or by right clicking on the binary view window, you can select the option \"Frida: Start Plugin\". Selecting this option will bring up a window asking you to select the device you want the plugin to use, any device supported by Frida should also be supported by this plugin. For example, Android and iOS devices should also appear listed here if they are plugged in via USB.\n\n#### Attach to Process\n\nOnce you have the process you want to analyse running, you can select the option \"Frida: Attach to Process\". Choosing this option will provide you with a list of currently running processes on the system you are targeting. Selecting one of those processes will trigger the plugin to use Frida to attach to that process.\n\n#### Select Module (Optional)\n\nBy default, this plugin will use the name of the binary you are analysing to select the target module. For example, if you currently have libssl.so loaded into Binary Ninja, then the plugin will look for that module in the process address. However, if the binary name cannot be found in the process' address space, then the module must be selected manually by running the \"Frida: Select Target Module\" menu option.\n\n#### Intercept Function\n\nNow that the plugin is running, you can start intercepting functions within the binary. To intercept a function all you need to do is right click within that function and select \"Frida: Intercept Function\". Providing there were no errors, that function will now be intercepted by Frida and any time that function is called a log message will be printed with the argument values and return value.\n\n#### Modify Intercept\n\nTo change the default behaviour for an intercepted function, you can use the option \"Frida: Modify Intercept\". Opening this window opens two Multiline input fields. Inside those fields you can enter JavaScript to be executed before and after the function has been run. Above each of the fields is a label to show what the existing hook looks like.\n\n#### Remove Intercept\n\nUsing \"Frida: Remove Intercept\" will safely remove the intercept from the Frida agent.\n\n#### Frida: Reload\n\nWhen modifying function information in Binary Ninja, such as parameter types, there is currently no way to be notified of these events. Therefore, to update the Frida intercepts, in these cases, you will need to manually call \"Frida: Reload\".\n\n#### Frida: Stop Plugin\n\nSafely removes all the hooks from the attached process, before disconnecting from the process.\n\n\n### Future:\n* Allow instruction level interception\n* Add support for using the Frida Stalker\n* Support process patching using Frida\n* Frida Spawn\n\n### Warnings:\n* The Binary Ninja interaction API is, currently, does not support injecting text into a Multiline Field. As a result, hook modification requires a user to retype what they had previously.", 14 | "license": { 15 | "name": "MIT", 16 | "text": "Copyright (c) 2017 Chame1eon\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." 17 | }, 18 | "dependencies": { 19 | "pip": ["frida"], 20 | "apt": [], 21 | "installers": [], 22 | "other": [] 23 | }, 24 | "version": "1.3.1", 25 | "author": "Chame1eon", 26 | "minimumbinaryninjaversion": 576 27 | } 28 | --------------------------------------------------------------------------------