Skip to content

Commit

Permalink
Add angr plugin entry and support for callbacks (#15)
Browse files Browse the repository at this point in the history
* Add angr plugin finder to generic template and work with change_watcher

* Fix the printer and add AM callbacks

* Fix thunk

* Fix menus popping up

* bump
  • Loading branch information
mahaloz authored Dec 28, 2023
1 parent 6d44b8d commit 16ebaf6
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 46 deletions.
20 changes: 13 additions & 7 deletions examples/change_watcher_plugin/bs_change_watcher/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@ def create_plugin(*args, **kwargs):
FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment
)

generic_printer = lambda *x, **y: print(f"Changed {x}{y}")
callback_handlers = {
typ: [generic_printer] for typ in (FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment,)
}
deci = DecompilerInterface.discover_interface(
plugin_name="ArtifactChangeWatcher",
init_plugin=True,
artifact_write_callbacks=callback_handlers,
ui_init_args=args,
ui_init_kwargs=kwargs
)
# create a function to print a string in the decompiler console
decompiler_printer = lambda *x, **y: deci.print(f"Changed {x}{y}")
# register the callback for all the types we want to print
deci.artifact_write_callbacks = {
typ: [decompiler_printer] for typ in (FunctionHeader, StackVariable, Enum, Struct, GlobalVariable, Comment,)
}

# register a ctx_menu_item late since we want the callback to be inside the deci
# register a menu to open when you right click on the psuedocode view
deci.register_ctx_menu_item(
"StartArtifactChangeWatcher",
"Start watching artifact changes",
Expand Down Expand Up @@ -85,5 +86,10 @@ def install_angr(self, path=None, interactive=True):
if not path:
return

path = path / "bs_change_watcher"
path.mkdir(parents=True, exist_ok=True)
src = self.pkg_path / "plugin.toml"
dst = Path(path) / "plugin.toml"
self.link_or_copy(src, dst, symlink=True)
self._copy_plugin_to_path(path)
return path
return path
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,28 @@ def create_plugin(*args, **kwargs):
has_ida = True
except ImportError:
has_ida = False
try:
import angrmanagement
has_angr = True
except ImportError:
has_angr = False

if not has_ida:
if not has_ida and not has_angr:
create_plugin()
elif has_angr:
from angrmanagement.plugins import BasePlugin
class AngrBSPluginThunk(BasePlugin):
def __init__(self, workspace):
super().__init__(workspace)
globals()["workspace"] = workspace
self.plugin = create_plugin()

def teardown(self):
pass


def PLUGIN_ENTRY(*args, **kwargs):
"""
This is the entry point for IDA to load the plugin.
"""
print("[+] Loading callback_watcher plugin (1/2)")
return create_plugin(*args, **kwargs)
13 changes: 13 additions & 0 deletions examples/change_watcher_plugin/bs_change_watcher/plugin.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[meta]
plugin_metadata_version = 0

[plugin]
name = "bs_change_watcher"
shortname = "bs_change_watcher"
version = "0.0.0"
description = ""
long_description = ""
platforms = ["windows", "linux", "macos"]
min_angr_version = "9.0.0.0"
author = "The BinSync Team"
entrypoints = ["bs_change_watcher_plugin.py"]
17 changes: 16 additions & 1 deletion examples/template_plugin_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,24 @@ def create_plugin(*args, **kwargs):
has_ida = True
except ImportError:
has_ida = False
try:
import angrmanagement
has_angr = True
except ImportError:
has_angr = False

if not has_ida:
if not has_ida and not has_angr:
create_plugin()
elif has_angr:
from angrmanagement.plugins import BasePlugin
class AngrBSPluginThunk(BasePlugin):
def __init__(self, workspace):
super().__init__(workspace)
globals()["workspace"] = workspace
self.plugin = create_plugin()

def teardown(self):
pass


def PLUGIN_ENTRY(*args, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion libbs/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.8.0"
__version__ = "0.9.0"
12 changes: 9 additions & 3 deletions libbs/api/decompiler_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def __init__(
self._gui_ctx_menu_actions = []
self._plugin_name = plugin_name
self.gui_plugin = None
self._artifact_watchers_started = False

# locks
self.artifact_write_lock = threading.Lock()
Expand Down Expand Up @@ -120,7 +121,8 @@ def start_artifact_watchers(self):
@return:
"""
pass
self.info("Starting BinSync artifact watchers...")
self._artifact_watchers_started = True

def stop_artifact_watchers(self):
"""
Expand All @@ -129,7 +131,8 @@ def stop_artifact_watchers(self):
decompiler. This is useful for plugins that want to watch for changes in the decompiler and
react to them.
"""
pass
self.info("Stopping BinSync artifact watchers...")
self._artifact_watchers_started = False

def _init_ui_components(self, *args, **kwargs):
from libbs.ui.version import set_ui_version
Expand Down Expand Up @@ -591,9 +594,12 @@ def artifact_set_event_handler(
return had_changes

#
# Special Loggers
# Special Loggers and Printers
#

def print(self, msg: str, **kwargs):
print(msg)

def info(self, msg: str, **kwargs):
_l.info(msg)

Expand Down
65 changes: 53 additions & 12 deletions libbs/decompilers/angr/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from angrmanagement.plugins import BasePlugin
from angrmanagement.ui.workspace import Workspace

from libbs.data import (
StackVariable, FunctionHeader, Enum, Struct, GlobalVariable, Comment, FunctionArgument
)

if typing.TYPE_CHECKING:
from .interface import AngrInterface

Expand Down Expand Up @@ -89,46 +93,83 @@ def handle_stack_var_renamed(self, func, offset, old_name, new_name):

decompilation = self.interface.decompile_function(func)
stack_var = self.interface.find_stack_var_in_codegen(decompilation, offset)
var_type = AngrInterface.stack_var_type_str(decompilation, stack_var)
return False
self.interface.stack_variable_changed(StackVariable(offset, new_name, None, stack_var.size, func.addr))
return True

# pylint: disable=unused-argument
def handle_stack_var_retyped(self, func, offset, old_type, new_type):
decompilation = self.interface.decompile_function(func)
stack_var = self.interface.find_stack_var_in_codegen(decompilation, offset)
return False
var_type = AngrInterface.stack_var_type_str(decompilation, stack_var)
self.interface.stack_variable_changed(StackVariable(offset, stack_var.name, var_type, stack_var.size, func.addr))
return True

# pylint: disable=unused-argument
def handle_func_arg_renamed(self, func, offset, old_name, new_name):
decompilation = self.interface.decompile_function(func)
func_args = AngrInterface.func_args_as_libbs_args(decompilation)
func_type = decompilation.cfunc.functy.returnty.c_repr()
return False
self.interface.function_header_changed(
FunctionHeader(
None,
func.addr,
type_=None,
args={offset: FunctionArgument(offset, new_name, None, func_args[offset].size)},
)
)

return True

# pylint: disable=unused-argument
def handle_func_arg_retyped(self, func, offset, old_type, new_type):
decompilation = self.interface.decompile_function(func)
func_args = AngrInterface.func_args_as_libbs_args(decompilation)
func_type = decompilation.cfunc.functy.returnty.c_repr()
return False
self.interface.function_header_changed(
FunctionHeader(
None,
func.addr,
type_=None,
args={offset: FunctionArgument(offset, None, new_type, func_args[offset].size)},
)
)

return True

# pylint: disable=unused-argument,no-self-use
def handle_global_var_renamed(self, address, old_name, new_name):
return False
self.interface.global_variable_changed(
GlobalVariable(address, new_name, type_=None)
)
return True

# pylint: disable=unused-argument,no-self-use
def handle_global_var_retyped(self, address, old_type, new_type):
return False
self.interface.global_variable_changed(
GlobalVariable(address, None, type_=new_type)
)
return True

# pylint: disable=unused-argument
def handle_function_renamed(self, func, old_name, new_name):
return False
if func is None:
return False

self.interface.function_header_changed(FunctionHeader(new_name, func.addr))
return True

# pylint: disable=unused-argument,no-self-use
def handle_function_retyped(self, func, old_type, new_type):
return False
if func is None:
return False

self.interface.function_header_changed(FunctionHeader(None, func.addr, type_=new_type))
return True

# pylint: disable=unused-argument
def handle_comment_changed(self, address, old_cmt, new_cmt, created: bool, decomp: bool):
# comments are only possible in functions in AM
func_addr = self.interface.get_closest_function(address)
return False
if func_addr is None:
return False

self.interface.comment_changed(Comment(address, new_cmt, func_addr=func_addr, decompiled=True))
return True
Loading

0 comments on commit 16ebaf6

Please sign in to comment.