diff options
| -rw-r--r-- | binaryninjaapi.h | 16 | ||||
| -rw-r--r-- | binaryninjacore.h | 8 | ||||
| -rw-r--r-- | plugin.cpp | 64 | ||||
| -rw-r--r-- | python/plugin.py | 90 | ||||
| -rw-r--r-- | rust/src/command.rs | 65 | ||||
| -rw-r--r-- | ui/action.h | 6 | ||||
| -rw-r--r-- | ui/projectbrowser.h | 1 |
7 files changed, 220 insertions, 30 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h index 5b435cf8..bab743d3 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -14757,6 +14757,7 @@ namespace BinaryNinja { Ref<LowLevelILFunction> lowLevelILFunction; Ref<MediumLevelILFunction> mediumLevelILFunction; Ref<HighLevelILFunction> highLevelILFunction; + Ref<Project> project; PluginCommandContext(); }; @@ -14833,6 +14834,12 @@ namespace BinaryNinja { std::function<bool(BinaryView*, const HighLevelILInstruction&)> isValid; }; + struct RegisteredProjectCommand + { + std::function<void(Project*)> action; + std::function<bool(Project*)> isValid; + }; + static void DefaultPluginCommandActionCallback(void* ctxt, BNBinaryView* view); static void AddressPluginCommandActionCallback(void* ctxt, BNBinaryView* view, uint64_t addr); static void RangePluginCommandActionCallback(void* ctxt, BNBinaryView* view, uint64_t addr, uint64_t len); @@ -14849,6 +14856,7 @@ namespace BinaryNinja { void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func); static void HighLevelILInstructionPluginCommandActionCallback( void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func, size_t instr); + static void ProjectPluginCommandActionCallback(void* ctxt, BNProject* project); static bool DefaultPluginCommandIsValidCallback(void* ctxt, BNBinaryView* view); static bool AddressPluginCommandIsValidCallback(void* ctxt, BNBinaryView* view, uint64_t addr); @@ -14866,6 +14874,7 @@ namespace BinaryNinja { void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func); static bool HighLevelILInstructionPluginCommandIsValidCallback( void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func, size_t instr); + static bool ProjectPluginCommandIsValidCallback(void* ctxt, BNProject* project); public: PluginCommand(const BNPluginCommand& cmd); @@ -15692,6 +15701,13 @@ namespace BinaryNinja { const std::function<void(BinaryView* view, const HighLevelILInstruction& instr)>& action, const std::function<bool(BinaryView* view, const HighLevelILInstruction& instr)>& isValid); + static void RegisterForProject(const std::string& name, const std::string& description, + const std::function<void(Project* project)>& action); + + static void RegisterForProject(const std::string& name, const std::string& description, + const std::function<void(Project* project)>& action, + const std::function<bool(Project* project)>& isValid); + /*! Get the list of registered PluginCommands \return The list of registered PluginCommands diff --git a/binaryninjacore.h b/binaryninjacore.h index 48b767f8..deca98e5 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -2593,7 +2593,8 @@ extern "C" MediumLevelILFunctionPluginCommand, MediumLevelILInstructionPluginCommand, HighLevelILFunctionPluginCommand, - HighLevelILInstructionPluginCommand + HighLevelILInstructionPluginCommand, + ProjectPluginCommand } BNPluginCommandType; typedef struct BNPluginCommand @@ -2615,6 +2616,7 @@ extern "C" void (*highLevelILFunctionCommand)(void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func); void (*highLevelILInstructionCommand)( void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func, size_t instr); + void (*projectCommand)(void* ctxt, BNProject* view); bool (*defaultIsValid)(void* ctxt, BNBinaryView* view); bool (*addressIsValid)(void* ctxt, BNBinaryView* view, uint64_t addr); @@ -2628,6 +2630,7 @@ extern "C" bool (*highLevelILFunctionIsValid)(void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func); bool (*highLevelILInstructionIsValid)( void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func, size_t instr); + bool (*projectIsValid)(void* ctxt, BNProject* view); } BNPluginCommand; typedef struct BNCustomCallingConvention @@ -6987,6 +6990,8 @@ extern "C" BINARYNINJACOREAPI void BNRegisterPluginCommandForHighLevelILInstruction(const char* name, const char* description, void (*action)(void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func, size_t instr), bool (*isValid)(void* ctxt, BNBinaryView* view, BNHighLevelILFunction* func, size_t instr), void* context); + BINARYNINJACOREAPI void BNRegisterPluginCommandForProject(const char* name, const char* description, + void (*action)(void* ctxt, BNProject* project), bool (*isValid)(void* ctxt, BNProject* project), void* context); BINARYNINJACOREAPI BNPluginCommand* BNGetAllPluginCommands(size_t* count); BINARYNINJACOREAPI BNPluginCommand* BNGetValidPluginCommands(BNBinaryView* view, size_t* count); @@ -7008,6 +7013,7 @@ extern "C" BNBinaryView* view, BNHighLevelILFunction* func, size_t* count); BINARYNINJACOREAPI BNPluginCommand* BNGetValidPluginCommandsForHighLevelILInstruction( BNBinaryView* view, BNHighLevelILFunction* func, size_t instr, size_t* count); + BINARYNINJACOREAPI BNPluginCommand* BNGetValidPluginCommandsForProject(BNProject* project, size_t* count); BINARYNINJACOREAPI void BNFreePluginCommandList(BNPluginCommand* commands); // Calling conventions @@ -164,6 +164,14 @@ void PluginCommand::HighLevelILInstructionPluginCommandActionCallback( } +void PluginCommand::ProjectPluginCommandActionCallback(void *ctxt, BNProject* project) +{ + RegisteredProjectCommand* cmd = (RegisteredProjectCommand*)ctxt; + Ref<Project> projectObject = new Project(BNNewProjectReference(project)); + return cmd->action(projectObject); +} + + bool PluginCommand::DefaultPluginCommandIsValidCallback(void* ctxt, BNBinaryView* view) { RegisteredDefaultCommand* cmd = (RegisteredDefaultCommand*)ctxt; @@ -260,6 +268,14 @@ bool PluginCommand::HighLevelILInstructionPluginCommandIsValidCallback( } +bool PluginCommand::ProjectPluginCommandIsValidCallback(void* ctxt, BNProject* project) +{ + RegisteredProjectCommand* cmd = (RegisteredProjectCommand*)ctxt; + Ref<Project> projectObject = new Project(BNNewProjectReference(project)); + return cmd->isValid(projectObject); +} + + void PluginCommand::Register( const string& name, const string& description, const function<void(BinaryView* view)>& action) { @@ -452,6 +468,22 @@ void PluginCommand::RegisterForHighLevelILInstruction(const string& name, const HighLevelILInstructionPluginCommandActionCallback, HighLevelILInstructionPluginCommandIsValidCallback, cmd); } +void PluginCommand::RegisterForProject(const std::string &name, const std::string &description, + const std::function<void(Project *project)> &action) +{ + RegisterForProject(name, description, action, [](Project*) { return true; }); +} + +void PluginCommand::RegisterForProject(const std::string &name, const std::string &description, + const std::function<void(Project* project)> &action, const std::function<bool(Project* project)> &isValid) +{ + RegisteredProjectCommand* cmd = new RegisteredProjectCommand; + cmd->action = action; + cmd->isValid = isValid; + BNRegisterPluginCommandForProject(name.c_str(), description.c_str(), ProjectPluginCommandActionCallback, + ProjectPluginCommandIsValidCallback, cmd); +} + vector<PluginCommand> PluginCommand::GetList() { @@ -481,40 +513,41 @@ vector<PluginCommand> PluginCommand::GetValidList(const PluginCommandContext& ct bool PluginCommand::IsValid(const PluginCommandContext& ctxt) const { - if (!ctxt.binaryView) - return false; - switch (m_command.type) { case DefaultPluginCommand: + if (!ctxt.binaryView) + return false; if (!m_command.defaultIsValid) return true; return m_command.defaultIsValid(m_command.context, ctxt.binaryView->GetObject()); case AddressPluginCommand: + if (!ctxt.binaryView) + return false; if (!m_command.addressIsValid) return true; return m_command.addressIsValid(m_command.context, ctxt.binaryView->GetObject(), ctxt.address); case RangePluginCommand: - if (ctxt.length == 0) + if (ctxt.length == 0 || !ctxt.binaryView) return false; if (!m_command.rangeIsValid) return true; return m_command.rangeIsValid(m_command.context, ctxt.binaryView->GetObject(), ctxt.address, ctxt.length); case FunctionPluginCommand: - if (!ctxt.function) + if (!ctxt.function || !ctxt.binaryView) return false; if (!m_command.functionIsValid) return true; return m_command.functionIsValid(m_command.context, ctxt.binaryView->GetObject(), ctxt.function->GetObject()); case LowLevelILFunctionPluginCommand: - if (!ctxt.lowLevelILFunction) + if (!ctxt.lowLevelILFunction || !ctxt.binaryView) return false; if (!m_command.lowLevelILFunctionIsValid) return true; return m_command.lowLevelILFunctionIsValid( m_command.context, ctxt.binaryView->GetObject(), ctxt.lowLevelILFunction->GetObject()); case LowLevelILInstructionPluginCommand: - if (!ctxt.lowLevelILFunction) + if (!ctxt.lowLevelILFunction || !ctxt.binaryView) return false; if (ctxt.instrIndex == BN_INVALID_EXPR) return false; @@ -523,14 +556,14 @@ bool PluginCommand::IsValid(const PluginCommandContext& ctxt) const return m_command.lowLevelILInstructionIsValid( m_command.context, ctxt.binaryView->GetObject(), ctxt.lowLevelILFunction->GetObject(), ctxt.instrIndex); case MediumLevelILFunctionPluginCommand: - if (!ctxt.mediumLevelILFunction) + if (!ctxt.mediumLevelILFunction || !ctxt.binaryView) return false; if (!m_command.mediumLevelILFunctionIsValid) return true; return m_command.mediumLevelILFunctionIsValid( m_command.context, ctxt.binaryView->GetObject(), ctxt.mediumLevelILFunction->GetObject()); case MediumLevelILInstructionPluginCommand: - if (!ctxt.mediumLevelILFunction) + if (!ctxt.mediumLevelILFunction || !ctxt.binaryView) return false; if (ctxt.instrIndex == BN_INVALID_EXPR) return false; @@ -539,14 +572,14 @@ bool PluginCommand::IsValid(const PluginCommandContext& ctxt) const return m_command.mediumLevelILInstructionIsValid( m_command.context, ctxt.binaryView->GetObject(), ctxt.mediumLevelILFunction->GetObject(), ctxt.instrIndex); case HighLevelILFunctionPluginCommand: - if (!ctxt.highLevelILFunction) + if (!ctxt.highLevelILFunction || !ctxt.binaryView) return false; if (!m_command.highLevelILFunctionIsValid) return true; return m_command.highLevelILFunctionIsValid( m_command.context, ctxt.binaryView->GetObject(), ctxt.highLevelILFunction->GetObject()); case HighLevelILInstructionPluginCommand: - if (!ctxt.highLevelILFunction) + if (!ctxt.highLevelILFunction || !ctxt.binaryView) return false; if (ctxt.instrIndex == BN_INVALID_EXPR) return false; @@ -554,6 +587,12 @@ bool PluginCommand::IsValid(const PluginCommandContext& ctxt) const return true; return m_command.highLevelILInstructionIsValid( m_command.context, ctxt.binaryView->GetObject(), ctxt.highLevelILFunction->GetObject(), ctxt.instrIndex); + case ProjectPluginCommand: + if (!m_command.projectIsValid) + return true; + if (!ctxt.project) + return false; + return m_command.projectIsValid(m_command.context, ctxt.project->GetObject()); default: return false; } @@ -603,6 +642,9 @@ void PluginCommand::Execute(const PluginCommandContext& ctxt) const m_command.highLevelILInstructionCommand( m_command.context, ctxt.binaryView->GetObject(), ctxt.highLevelILFunction->GetObject(), ctxt.instrIndex); break; + case ProjectPluginCommand: + m_command.projectCommand(m_command.context, ctxt.project->GetObject()); + break; default: break; } diff --git a/python/plugin.py b/python/plugin.py index 72f3385d..26fde9cb 100644 --- a/python/plugin.py +++ b/python/plugin.py @@ -18,23 +18,24 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -import traceback import ctypes import threading - +import traceback from typing import Optional, Callable # Binary Ninja components import binaryninja + from . import _binaryninjacore as core -from .enums import PluginCommandType -from . import filemetadata from . import binaryview +from . import filemetadata from . import function -from .log import log_error +from . import highlevelil from . import lowlevelil from . import mediumlevelil -from . import highlevelil +from . import project +from .enums import PluginCommandType +from .log import log_error class PluginCommandContext: @@ -55,6 +56,7 @@ class PluginCommandContext: self._length = 0 self._function = None self._instruction = None + self._project = view.project def __len__(self): return self._length @@ -99,6 +101,14 @@ class PluginCommandContext: def instruction(self, value): self._instruction = value + @property + def project(self): + return self._project + + @function.setter + def project(self, value): + self._project = value + class _PluginCommandMetaClass(type): def __iter__(self): @@ -238,6 +248,14 @@ class PluginCommand(metaclass=_PluginCommandMetaClass): log_error(traceback.format_exc()) @staticmethod + def _project_action(project, action): + try: + project_obj = project.Project(handle=core.BNNewProjectReference(project)) + action(project_obj) + except: + log_error(traceback.format_exc()) + + @staticmethod def _default_is_valid(view, is_valid): try: if is_valid is None: @@ -380,6 +398,17 @@ class PluginCommand(metaclass=_PluginCommandMetaClass): log_error(traceback.format_exc()) return False + @staticmethod + def _project_is_valid(project, is_valid): + try: + if is_valid is None: + return True + project_obj = project.Project(handle=core.BNNewProjectReference(project)) + return is_valid(project_obj) + except: + log_error(traceback.format_exc()) + return False + @classmethod def register( cls, name: str, description: str, action: Callable[['binaryview.BinaryView'], None], @@ -756,7 +785,44 @@ class PluginCommand(metaclass=_PluginCommandMetaClass): core.BNRegisterPluginCommandForHighLevelILInstruction(name, description, action_obj, is_valid_obj, None) @classmethod - def get_valid_list(cls, context): + def register_for_project( + cls, name: str, description: str, action: Callable[['project.Project'], None], + is_valid: Optional[Callable[['project.Project'], bool]] = None + ): + r""" + ``register_for_project`` Register a plugin to be called with a project argument + + :param str name: name of the plugin (use 'Folder\\Name' to have the menu item nested in a folder) + :param str description: description of the plugin + :param callback action: function to call with the :class:`~project.Project` as an argument + :param callback is_valid: optional argument of a function passed a :class:`~project.Project` to determine whether the plugin should be enabled for that project + :rtype: None + :Example: + + >>> def my_plugin(project: Project): + >>> log_info(f"My plugin was called on project: `{project}`") + >>> PluginCommand.register_for_project("My Plugin", "My plugin description (not used)", my_plugin) + True + >>> def is_valid(project: Project) -> bool: + >>> return False + >>> PluginCommand.register_for_project("My Plugin (With Valid Function)", "My plugin description (not used)", my_plugin, is_valid) + True + + .. warning:: Calling ``register_for_project`` with the same function name will replace the existing function but will leak the memory of the original plugin. + """ + binaryninja._init_plugins() + action_obj = ctypes.CFUNCTYPE(None, ctypes.c_void_p, + ctypes.POINTER(core.BNProject + ))(lambda ctxt, project: cls._project_action(project, action)) + is_valid_obj = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_void_p, + ctypes.POINTER(core.BNProject + ))( + lambda ctxt, project: cls._project_is_valid(project, is_valid)) + cls._registered_commands.append((action_obj, is_valid_obj)) + core.BNRegisterPluginCommandForProject(name, description, action_obj, is_valid_obj, None) + + @classmethod + def get_valid_list(cls, context: PluginCommandContext): """Dict of registered plugins""" commands = list(cls) result = {} @@ -765,7 +831,7 @@ class PluginCommand(metaclass=_PluginCommandMetaClass): result[cmd.name] = cmd return result - def is_valid(self, context): + def is_valid(self, context: PluginCommandContext): if context.view is None: return False if self._command.type == PluginCommandType.DefaultPluginCommand: @@ -847,9 +913,13 @@ class PluginCommand(metaclass=_PluginCommandMetaClass): self._command.context, context.view.handle, context.instruction.function.handle, context.instruction.instr_index ) + elif self._command.type == PluginCommandType.ProjectPluginCommand: + if not self._command.projectIsValid: + return True + return self._command.projectIsValid(self._command.context, context.project.handle) return False - def execute(self, context): + def execute(self, context: PluginCommandContext): r""" ``execute`` Execute a plugin. See the example in :class:`~PluginCommandContext` @@ -895,6 +965,8 @@ class PluginCommand(metaclass=_PluginCommandMetaClass): self._command.context, context.view.handle, context.instruction.function.handle, context.instruction.instr_index ) + elif self._command.type == PluginCommandType.ProjectPluginCommand: + self._command.projectCommand(self._command.context, context.project.handle) def __repr__(self): return "<PluginCommand: %s>" % self._name diff --git a/rust/src/command.rs b/rust/src/command.rs index 46f43bbd..eecdf4a8 100644 --- a/rust/src/command.rs +++ b/rust/src/command.rs @@ -33,16 +33,18 @@ //! The return value of these functions should indicate whether they successfully initialized themselves. use binaryninjacore_sys::{ - BNBinaryView, BNFunction, BNRegisterPluginCommand, BNRegisterPluginCommandForAddress, - BNRegisterPluginCommandForFunction, BNRegisterPluginCommandForRange, + BNBinaryView, BNFunction, BNProject, BNRegisterPluginCommand, + BNRegisterPluginCommandForAddress, BNRegisterPluginCommandForFunction, + BNRegisterPluginCommandForProject, BNRegisterPluginCommandForRange, }; -use std::ops::Range; -use std::os::raw::c_void; - use crate::binary_view::BinaryView; use crate::function::Function; +use crate::project::Project; use crate::string::IntoCStr; +use std::ops::Range; +use std::os::raw::c_void; +use std::ptr::NonNull; /// The trait required for generic commands. See [register_command] for example usage. pub trait Command: 'static + Sync { @@ -451,3 +453,56 @@ pub fn register_command_for_function<C: FunctionCommand>(name: &str, desc: &str, ); } } + +pub trait ProjectCommand: 'static + Sync { + fn action(&self, project: &Project); + fn valid(&self, project: &Project) -> bool; +} + +pub fn register_command_for_project<C: ProjectCommand>(name: &str, desc: &str, command: C) { + extern "C" fn cb_action<C>(ctxt: *mut c_void, project: *mut BNProject) + where + C: ProjectCommand, + { + ffi_wrap!("Command::action", unsafe { + let cmd = &*(ctxt as *const C); + + let handle = NonNull::new(project).expect("project handle is null"); + let project = Project { handle }; + + cmd.action(&project); + }) + } + + extern "C" fn cb_valid<C>(ctxt: *mut c_void, project: *mut BNProject) -> bool + where + C: ProjectCommand, + { + ffi_wrap!("Command::valid", unsafe { + let cmd = &*(ctxt as *const C); + + let handle = NonNull::new(project).expect("project handle is null"); + let project = Project { handle }; + + cmd.valid(&project) + }) + } + + let name = name.to_cstr(); + let desc = desc.to_cstr(); + + let name_ptr = name.as_ptr(); + let desc_ptr = desc.as_ptr(); + + let ctxt = Box::into_raw(Box::new(command)); + + unsafe { + BNRegisterPluginCommandForProject( + name_ptr, + desc_ptr, + Some(cb_action::<C>), + Some(cb_valid::<C>), + ctxt as *mut _, + ); + } +} diff --git a/ui/action.h b/ui/action.h index c7357fcf..238121ec 100644 --- a/ui/action.h +++ b/ui/action.h @@ -353,10 +353,8 @@ class BINARYNINJAUIAPI Menu void removeTransformActions(); void addPluginCommandActions(const QString& group); - void addPluginCommandActions(const QString& prefix, const QString& group, uint8_t order = MENU_ORDER_NORMAL); - void addPluginCommandSubmenuActions(const QString& submenu, const QString& group); - void addPluginCommandSubmenuActions( - const QString& submenu, const QString& prefix, const QString& group, uint8_t order = MENU_ORDER_NORMAL); + void addPluginCommandActions(const QString& prefix, const QString& group, uint8_t order = MENU_ORDER_NORMAL, const std::vector<BNPluginCommandType>& types = {}); + void addPluginCommandSubmenuActions(const QString& submenu, const QString& group, const std::vector<BNPluginCommandType>& types); void removePluginCommandActions(); void removePluginCommandSubmenuActions(const QString& submenu); diff --git a/ui/projectbrowser.h b/ui/projectbrowser.h index da6699e1..f4f9bd4c 100644 --- a/ui/projectbrowser.h +++ b/ui/projectbrowser.h @@ -323,6 +323,7 @@ class BINARYNINJAUIAPI ProjectBrowser: public QWidget, public UIContextNotificat void restoreTreeState(); void initActions(); + UIActionContext actionContext(); std::vector<ProjectFileRef> GetSelectedFilesRecursive(); |
