Skip to content

Commit

Permalink
feat: add plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
EliseZeroTwo committed Feb 16, 2022
1 parent c26207f commit 40d5356
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 37 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) <year> <copyright holders>
Copyright (c) 2022 EliseZeroTwo

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:

Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Sample Plugin
Author: **Vector 35 Inc**
# SEH Helper

_This is a short description meant to fit on one line._
Author: **EliseZeroTwo**

_A Binary Ninja helper for exploring structured exception handlers in PEs_

## Description:
This is a longer description meant for a sample plugin that demonstrates the metadata format for Binary Ninja plugins. Note that the [community-plugins repo](https://github.com/Vector35/community-plugins) contains a useful [utility](https://github.com/Vector35/community-plugins/blob/master/generate_plugininfo.py) to validate the plugin.json. Additionally, the [release helper](https://github.com/Vector35/release_helper) plugin is helpful for more easily pushing new releases, incrementing versions, and creating the appropriate GitHub tags.

Note that originally we recommended specifying the contents of this entire file inside of the [plugin.json](./plugin.json) but the latest repository generator will use the readme contents directly which means you should simply leave an empty longdescription field.
This plugin provides a UI helper for exploring structured exception handlers in PEs. It provides a feature to view all entries, view the entry at the cursor, or follow the cursor displaying the entry at the cursor constantly.

## License

This plugin is released under an [MIT license](./license).

![SEH Demo Image](./images/demo.png)
246 changes: 242 additions & 4 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,245 @@
from binaryninja import *
from binaryninjaui import WidgetPane, UIActionHandler, UIActionHandler, UIAction, Menu, UIContext, UIContextNotification
from pefile import ExceptionsDirEntryData, PE, PEFormatError
from PySide6 import QtCore
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget, QListWidget, QListWidgetItem, QTextEdit, QCheckBox, QPushButton
from PySide6.QtGui import QMouseEvent

def do_nothing(bv,function):
show_message_box("Do Nothing", "Congratulations! You have successfully done nothing.\n\n" +
"Pat yourself on the back.", MessageBoxButtonSet.OKButtonSet, MessageBoxIcon.ErrorIcon)

PluginCommand.register_for_address("Useless Plugin", "Basically does nothing", do_nothing)
class SEHListItem(QListWidgetItem):
def __init__(self, base: int, entry: ExceptionsDirEntryData):
self.entry = entry
QListWidgetItem.__init__(self, hex(
base + entry.struct.BeginAddress) + "-" + hex(base + entry.struct.EndAddress))


class AddrLabel(QLabel):
def __init__(self, address, bv: BinaryView):
self.bv = bv
self.addr = address
if self.addr != None:
QLabel.__init__(self, hex(self.addr))
else:
QLabel.__init__(self, "")

def mouseReleaseEvent(self, event: QMouseEvent) -> None:
button = event.button()
modifiers = event.modifiers()
if modifiers == Qt.NoModifier and button == Qt.LeftButton:
if self.addr != None:
self.bv.offset = self.addr
return super().mouseReleaseEvent(event)

def setAddr(self, addr):
self.addr = addr
if self.addr != None:
self.setText(hex(self.addr))
else:
self.setText("")


class SEHNotifications(UIContextNotification):
def __init__(self, widget):
UIContextNotification.__init__(self)
self.widget = widget
self.widget.destroyed.connect(self.destroyed)
UIContext.registerNotification(self)

def destroyed(self):
UIContext.unregisterNotification(self)

def OnAddressChange(self, context, frame, view, location):
if self.widget.follow_cb.isChecked():
self.widget.gotoAddr(location.getOffset())


class SEHWidget(QWidget, UIContextNotification):
def gotoAddr(self, addr):
for x in self.dict:
if x[0] < addr < x[1]:
row = self.dict[x]
self.list.setCurrentRow(row)
self.listItemClicked(self.list.item(row))
break

def gotoButtonClicked(self):
self.gotoAddr(self.bv.offset)

def __init__(self, bv: BinaryView, file: PE):
QWidget.__init__(self)
layout = QVBoxLayout()

header_layout = QHBoxLayout()

self.follow_cb = QCheckBox()
follow_layout = QHBoxLayout()
follow_layout.addWidget(QLabel("Follow Cursor: "))
follow_layout.addWidget(self.follow_cb)

goto_button = QPushButton()
goto_button.setText("Goto Cursor")
goto_button.clicked.connect(self.gotoButtonClicked)

header_layout.addWidget(goto_button)
header_layout.addStretch()
header_layout.addLayout(follow_layout)

self.begin_addr = AddrLabel(None, bv)
begin_addr_layout = QHBoxLayout()
begin_addr_layout.addWidget(QLabel("Begin Address: "))
begin_addr_layout.addWidget(self.begin_addr)

self.end_addr = AddrLabel(None, bv)
end_addr_layout = QHBoxLayout()
end_addr_layout.addWidget(QLabel("End Address: "))
end_addr_layout.addWidget(self.end_addr)

self.unwind_addr = AddrLabel(None, bv)
unwind_addr_layout = QHBoxLayout()
unwind_addr_layout.addWidget(QLabel("Unwind Address: "))
unwind_addr_layout.addWidget(self.unwind_addr)

unwind_layout = QVBoxLayout()
self.unwind_version = QLabel("")
self.unwind_prolog_size = QLabel("")
self.unwind_code_count = QLabel("")
self.unwind_frame_register = QLabel("")
self.unwind_frame_offset = QLabel("")
self.unwind_flags = QLabel("")
self.unwind_codes = QTextEdit()
self.unwind_codes.setReadOnly(True)
self.unwind_exception_handler = AddrLabel(None, bv)

title = QLabel("Unwind Info")
title.setAlignment(QtCore.Qt.AlignCenter)
unwind_layout.addWidget(title)

unwind_verison_layout = QHBoxLayout()
unwind_verison_layout.addWidget(QLabel("Version: "))
unwind_verison_layout.addWidget(self.unwind_version)
unwind_layout.addLayout(unwind_verison_layout)

unwind_flags_layout = QHBoxLayout()
unwind_flags_layout.addWidget(QLabel("Flags: "))
unwind_flags_layout.addWidget(self.unwind_flags)
unwind_layout.addLayout(unwind_flags_layout)

unwind_exception_handler_layout = QHBoxLayout()
unwind_exception_handler_layout.addWidget(
QLabel("Exception Handler: "))
unwind_exception_handler_layout.addWidget(
self.unwind_exception_handler)
unwind_layout.addLayout(unwind_exception_handler_layout)

unwind_prolog_size_layout = QHBoxLayout()
unwind_prolog_size_layout.addWidget(QLabel("Prolog Size: "))
unwind_prolog_size_layout.addWidget(self.unwind_prolog_size)
unwind_layout.addLayout(unwind_prolog_size_layout)

unwind_code_count_layout = QHBoxLayout()
unwind_code_count_layout.addWidget(QLabel("Code Count: "))
unwind_code_count_layout.addWidget(self.unwind_code_count)
unwind_layout.addLayout(unwind_code_count_layout)

unwind_frame_register_layout = QHBoxLayout()
unwind_frame_register_layout.addWidget(QLabel("Frame Register: "))
unwind_frame_register_layout.addWidget(self.unwind_frame_register)
unwind_layout.addLayout(unwind_frame_register_layout)

unwind_frame_offset_layout = QHBoxLayout()
unwind_frame_offset_layout.addWidget(QLabel("Frame Offset: "))
unwind_frame_offset_layout.addWidget(self.unwind_frame_offset)
unwind_layout.addLayout(unwind_frame_offset_layout)

unwind_codes_layout = QHBoxLayout()
unwind_codes_layout.addWidget(QLabel("Codes: "))
unwind_codes_layout.addWidget(self.unwind_codes)
unwind_layout.addLayout(unwind_codes_layout)

self.dict = {}
self.list = QListWidget()
self.list.currentItemChanged.connect(self.listItemClicked)
ctr = 0
for entry in file.DIRECTORY_ENTRY_EXCEPTION:
item = SEHListItem(file.OPTIONAL_HEADER.ImageBase, entry)
self.list.addItem(item)
self.dict[(file.OPTIONAL_HEADER.ImageBase + entry.struct.BeginAddress,
file.OPTIONAL_HEADER.ImageBase + entry.struct.EndAddress)] = ctr
ctr += 1

list_layout = QHBoxLayout()
list_layout.addWidget(QLabel("Entries: "))
list_layout.addWidget(self.list)

layout.addLayout(header_layout)
layout.addLayout(list_layout)
layout.addLayout(begin_addr_layout)
layout.addLayout(end_addr_layout)
layout.addLayout(unwind_addr_layout)
layout.addLayout(unwind_layout)
self.setLayout(layout)

self.file = file
self.bv = bv

self.notifications = SEHNotifications(self)

def listItemClicked(self, clickedItem: SEHListItem):
self.begin_addr.setAddr(
self.file.OPTIONAL_HEADER.ImageBase + clickedItem.entry.struct.BeginAddress)
self.end_addr.setAddr(
self.file.OPTIONAL_HEADER.ImageBase + clickedItem.entry.struct.EndAddress)

self.unwind_version.setText(str(clickedItem.entry.unwindinfo.Version))
self.unwind_flags.setText(str(clickedItem.entry.unwindinfo.Flags))
self.unwind_prolog_size.setText(
str(clickedItem.entry.unwindinfo.SizeOfProlog))
self.unwind_code_count.setText(
str(clickedItem.entry.unwindinfo.CountOfCodes))
self.unwind_frame_register.setText(
str(clickedItem.entry.unwindinfo.FrameRegister))
self.unwind_frame_offset.setText(
str(clickedItem.entry.unwindinfo.FrameOffset))
codes = ""
for x in clickedItem.entry.unwindinfo.UnwindCodes:
codes += str(x) + '\n'
self.unwind_codes.setText(codes)

if hasattr(clickedItem.entry.unwindinfo, 'ExceptionHandler'):
self.unwind_exception_handler.setAddr(
self.file.OPTIONAL_HEADER.ImageBase + clickedItem.entry.unwindinfo.ExceptionHandler)
else:
self.unwind_exception_handler.clear()

return

@staticmethod
def createPane(context):
if context.context and context.binaryView and context.binaryView.parent_view:
data = context.binaryView.parent_view.read(
0, context.binaryView.parent_view.length)
widget = SEHWidget(context.binaryView, PE(data=data))
pane = WidgetPane(widget, "Structured Exception Handlers")
context.context.openPane(pane)

@staticmethod
def canCreatePane(context):
if context.context and context.binaryView and context.binaryView.parent_view:
try:
data = context.binaryView.parent_view.read(
0, context.binaryView.parent_view.length)
PE(data=data, fast_load=True)
return True
except PEFormatError:
return False
except:
raise
return False


UIAction.registerAction("SEH Helper")
UIActionHandler.globalActions().bindAction(
"SEH Helper", UIAction(SEHWidget.createPane, SEHWidget.canCreatePane)
)
Menu.mainMenu("Tools").addAction("SEH Helper", "SEH Helper")
Binary file added images/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 15 additions & 27 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -1,51 +1,39 @@
{
"pluginmetadataversion": 2,
"name": "Sample Plugin",
"name": "SEH Helper",
"type": [
"core",
"ui",
"architecture",
"binaryview",
"helper"
"helper",
"ui"
],
"api": [
"python3"
],
"description": "This is a short description meant to fit on one line.",
"description": "Helper for exploring structured exception handlers in PEs",
"longdescription": "",
"license": {
"name": "MIT",
"text": "Copyright (c) <year> <copyright holders>\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."
"text": "Copyright (c) 2022 EliseZeroTwo\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."
},
"platforms": [
"Darwin",
"Linux",
"Windows"
],
"installinstructions": {
"Darwin": "Install the following pip packages: ...\n\nInstall the following brew packages: ...",
"Linux": "Install the following pip packages: ...\n\nInstall the following apt packages: ...",
"Windows": "Install the following pip packages: ...\n\nInstall the following libraries: ..."
"Darwin": "Install the following pip packages: pefile",
"Linux": "Install the following pip packages: pefile",
"Windows": "Install the following pip packages: pefile"
},
"dependencies": {
"pip": [
"array",
"of",
"pip",
"dependencies"
"pefile",
"pyside6"
],
"apt": [
"apt",
"packages"
],
"installers": [
"https://bogus-domain/this-package.exe"
],
"other": [
"The sample plugin requires [this random package](https://bogus-domain/this-package/) be installed."
]
"apt": [],
"installers": [],
"other": []
},
"version": "1.3.5",
"author": "Vector 35 Inc",
"version": "0.1.0",
"author": "EliseZeroTwo",
"minimumbinaryninjaversion": 3164
}

0 comments on commit 40d5356

Please sign in to comment.