# plugins

repository: osirislab/Fentanyl
file: main.py
description: Fentanyl is an IDAPython script that makes patching significantly easier
url: https://github.com/osirislab/Fentanyl/blob/07718b6e8e150fad2e627c339e9528c082186e34/main.py
comment: Fentanyl - IDA Multitool
created_at: 2013-11-13 03:08:02+00:00
pushed_at: 2022-10-12 05:57:10+00:00
content:
__AUTHOR__ = 'OSIRIS Lab'
PLUGIN_NAME = "Fentanyl"
VERSION = '2.0'

import idaapi
import src.py3.hooks as hooks

major, minor = map(int, idaapi.get_kernel_version().split("."))
using_ida7api = (major > 6)
using_pyqt5 = using_ida7api or (major == 6 and minor >= 9)

if using_pyqt5:
    import PyQt5.QtGui as QtGui
    import PyQt5.QtCore as QtCore

else:
    import PySide.QtGui as QtGui
    import PySide.QtCore as QtCore

    QtWidgets = QtGui
    QtCore.pyqtSignal = QtCore.Signal
    QtCore.pyqtSlot = QtCore.Slot


def PLUGIN_ENTRY():
    """
    Required plugin entry point for IDAPython Plugins.
    """
    return fentanyl()


class fentanyl(idaapi.plugin_t):
    """
    The IDA Plugin for Fentanyl.
    """

    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
    comment = "Fentanyl - IDA Multitool"
    help = "Visit ... for help."
    wanted_name = PLUGIN_NAME

    # --------------------------------------------------------------------------
    # Plugin Overloads
    # --------------------------------------------------------------------------

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """

        # initialize the menu actions our plugin will inject
        self._init_action_fentanyl()

        # initialize plugin hooks
        self._init_hooks()

        # done
        idaapi.msg("%s %s initialized...\n" % (self.wanted_name, VERSION))
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name)

    def term(self):
        """
        This is called by IDA when it is unloading the plugin.
        """

        # unhook our plugin hooks
        self._hooks.unhook()

        # unregister our actions & free their resources
        self._del_action_fentanyl()

        # done
        idaapi.msg("%s terminated...\n" % self.wanted_name)

    # --------------------------------------------------------------------------
    # Plugin Hooks
    # --------------------------------------------------------------------------

    def _init_hooks(self):
        """
        Install plugin hooks into IDA.
        """
        self._hooks = Hooks()
        self._hooks.ready_to_run = self._init_hexrays_hooks
        self._hooks.hook()

    def _init_hexrays_hooks(self):
        """
        Install Hex-Rrays hooks (when available).
        NOTE: This is called when the ui_ready_to_run event fires.
        """
        if idaapi.init_hexrays_plugin():
            idaapi.install_hexrays_callback(self._hooks.hxe_callback)

    def _init_action_fentanyl(self):
        for h in hooks.HOOKS:
            _action = idaapi.action_desc_t(
                h['prefix'],
                h['title'],
                h['hook'],
                h['hotkey'],
                h['desc'],
                h.get('icon', -1)
            )
            idaapi.register_action(_action)

    def _del_action_fentanyl(self):
        """
        Delete the bulk prefix action from IDA.
        """
        for h in hooks.HOOKS:
            idaapi.unregister_action(h['prefix'])


# ------------------------------------------------------------------------------
# Plugin Hooks
# ------------------------------------------------------------------------------

class Hooks(idaapi.UI_Hooks):
    def finish_populating_widget_popup(self, widget, popup):
        """
        A right click menu is about to be shown. (IDA 7)
        """
        inject_actions(widget, popup, idaapi.get_widget_type(widget))
        return 0

    def hxe_callback(self, event, *args):
        """
        HexRays event callback.
        We lump this under the (UI) Hooks class for organizational reasons.
        """

        #
        # if the event callback indicates that this is a popup menu event
        # (in the hexrays window), we may want to install our prefix menu
        # actions depending on what the cursor right clicked.
        #

        if event == idaapi.hxe_populating_popup:
            form, popup, vu = args
            for h in hooks.HOOKS:
                idaapi.attach_action_to_popup(
                    form,
                    popup,
                    h['prefix'],
                    h['title'],
                    idaapi.SETMENU_APP
                )
        # done
        return 0


# ------------------------------------------------------------------------------
# Prefix Wrappers
# ------------------------------------------------------------------------------


def inject_actions(form, popup, form_type):
    """
    Inject prefix actions to popup menu(s) based on context.
    """

    #
    # disassembly window
    #

    if form_type == idaapi.BWN_DISASMS:
        # insert the prefix action entry into the menu
        #
        for h in hooks.HOOKS:
            idaapi.attach_action_to_popup(
                form,
                popup,
                h['prefix'],
                h['title'],
                idaapi.SETMENU_APP
            )

    # done
    return 0


repository: McGill-DMaS/Kam1n0-Community
file: kam1n0-clients/ida-plugin/Kam1n0.py
description: The Kam1n0 Assembly Analysis Platform
created_at: 2015-09-09 09:11:13+00:00
pushed_at: 2023-03-02 15:32:10+00:00
url:
https://github.com/McGill-DMaS/Kam1n0-Community/blob/1c3c309acc8f5e50c6617f858aa3b5b84c121b26/kam1n0-clients/ida-plugin/Kam1n0.py

content:
# *******************************************************************************
#  * Copyright 2017 McGill University All rights reserved.
#  *
#  * Licensed under the Apache License, Version 2.0 (the "License");
#  * you may not use this file except in compliance with the License.
#  * You may obtain a copy of the License at
#  *
#  *     http://www.apache.org/licenses/LICENSE-2.0
#  *
#  * Unless required by applicable law or agreed to in writing, software
#  * distributed under the License is distributed on an "AS IS" BASIS,
#  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  * See the License for the specific language governing permissions and
#  * limitations under the License.
#  *******************************************************************************/
import idaapi
res = idaapi.get_plugin_options("kam1n0")
if res != 'noinit':
    import Kam1n0.Plugin as kp
    
    def PLUGIN_ENTRY():
        return kp.kam1n0_t()


repository: DGA-MI-SSI/YaCo
file: YaCo/yaco_plugin.py
wanted_name: YaCo
comment: YaCo plugin
created_at: 2017-05-29 14:18:31+00:00
pushed_at: 2019-08-05 18:21:32+00:00
description:
YaCo is an Hex-Rays IDA plugin. When enabled, multiple users can work simultaneously on the same binary. Any modification done by any user is synchronized through git version control.

url:
https://github.com/DGA-MI-SSI/YaCo/blob/9b85e6ca1809114c4df1382c11255f7e38408912/YaCo/yaco_plugin.py

content:
#   Copyright (C) 2017 The YaCo Authors
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

import idaapi
import idc
import inspect
import os
import sys
import traceback

yaco = None

def start():
    global yaco
    if idc.__EA64__:
        import YaToolsPy64 as ya
    else:
        import YaToolsPy32 as ya
    if not yaco:
        yaco = ya.make_yaco()


def close():
    global yaco
    yaco = None


def is_enabled():
    opts = idaapi.get_plugin_options("yaco")
    return not opts or "disable_plugin" not in opts.split(':')


class YaCoPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "YaCo plugin"
    help = "YaCo: Yet Another Collaboration tool plugin"
    wanted_name = "YaCo"
    wanted_hotkey = ""

    def init(self, *args, **kwargs):
        if not is_enabled():
            print("yaco: disabled")
            return idaapi.PLUGIN_SKIP

        root_dir = os.path.abspath(os.path.join(inspect.getsourcefile(lambda: 0), "..", "YaTools"))
        for path in ["YaCo", "bin"]:
            path = os.path.join(root_dir, path)
            print("yaco: using %s" % path)
            sys.path.append(path)

        input_filename = idc.GetIdbPath()
        if input_filename.count("_local.") > 0 and os.path.exists(".git"):
            print("yaco: initializing")
            start()
            if not yaco.is_started():
                return idaapi.PLUGIN_SKIP

            return idaapi.PLUGIN_KEEP

        if "_local." not in input_filename and os.path.exists(".git"):
            print("""
*******************************************************
WARNING : You have opened a database in a git project,
WARNING : but your database doesn't match a YaCo project.
WARNING : YaCo is disabled !
*******************************************************
""")
            return idaapi.PLUGIN_OK

        return idaapi.PLUGIN_KEEP

    def run(self, *args, **kwargs):
        start()

    def term(self, *args, **kwargs):
        close()


def PLUGIN_ENTRY():
    return YaCoPlugin()


repository: blackberry/pe_tree
file: pe_tree_ida.py
url: https://github.com/blackberry/pe_tree/blob/2be607fc55702293cd02cbc6cda5283452464aff/pe_tree_ida.py
created_at: 2020-07-27 13:37:30+00:00
pushed_at: 2022-07-09 03:36:38+00:00
description:
Python module for viewing Portable Executable (PE) files in a tree-view using pefile and PyQt5. Can also be used with IDA Pro and Rekall to dump in-memory PE files and reconstruct imports.

content:
#
#   Copyright (c) 2020 BlackBerry Limited.  All Rights Reserved.
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

"""PE Tree IDAPython plugin"""

# Standard imports
import os
import sys

# Qt imports
from PyQt5 import QtCore, QtWidgets

# IDAPython imports

# pylint: disable=import-error

import idaapi
import ida_idaapi
import idc
import ida_bytes
import ida_name
import ida_kernwin
import idautils
import sip

# PE Tree imports
if "pe_tree" in sys.modules:
    # Reload all PE Tree modules as IDA will keep them loaded
    try:
        from importlib import reload
    except ImportError:
        from imp import reload

    try:
        reload(pe_tree)
        reload(pe_tree.runtime)
        reload(pe_tree.form)
        reload(pe_tree.info)
        reload(pe_tree.contextmenu)
        reload(pe_tree.dump_pe)
        reload(pe_tree.exceptions)
        reload(pe_tree.hash_pe)
        reload(pe_tree.map)
        reload(pe_tree.qstandarditems)
        reload(pe_tree.tree)
        reload(pe_tree.utils)
        reload(pe_tree.dialogs)
    except NameError:
        pass

import pe_tree.runtime
import pe_tree.form
import pe_tree.info

class IDARuntime(pe_tree.runtime.Runtime):
    """IDA runtime callbacks"""
    def __init__(self, widget, args):
        # Load configuration
        self.config_file = os.path.join(self.get_temp_dir(), "pe_tree.ini")
        super(IDARuntime, self).__init__(widget, args)

    def get_temp_dir(self):
        """Get path to temporary directory"""
        self.ret = idaapi.get_user_idadir()
        return self.ret

    def ask_file(self, filename, caption, filter="", save=False):
        """Open/save file dialog"""
        if not save:
            # Open file dialog
            self.ret = ida_kernwin.ask_file(0, os.path.basename(str(filename)), str(caption))
        else:
            # Save file dialog
            if filename[0] == ".":
                # Remove leading dot from section names
                filename = filename[1:]

            self.ret = ida_kernwin.ask_file(1, os.path.basename(str(filename)), str(caption))

        return self.ret

    def show_widget(self):
        """Display the PE Tree widget"""
        self.ret = idaapi.display_widget(self.widget, (idaapi.PluginForm.WOPN_TAB | idaapi.PluginForm.WOPN_MENU | idaapi.PluginForm.WOPN_RESTORE | idaapi.PluginForm.WOPN_PERSIST))

        return self.ret

    @QtCore.pyqtSlot(object, int)
    def jumpto(self, item, offset):
        """Jump to offset in IDA view"""
        try:
            self.ret = idc.jumpto(offset)
        except:
            self.ret = False

        return self.ret

    @QtCore.pyqtSlot(str)
    def log(self, output):
        """Print to output"""
        print(output)

    @QtCore.pyqtSlot(object, object)
    def get_bytes(self, start, size):
        """Read bytes from IDB"""
        self.ret = idc.get_bytes(start, size)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_byte(self, offset):
        """Read byte from IDB"""
        self.ret = ida_bytes.get_byte(offset)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_word(self, offset):
        """Read word from IDB"""
        self.ret = ida_bytes.get_word(offset)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_dword(self, offset):
        """Read dword from IDB"""
        self.ret = ida_bytes.get_dword(offset)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_qword(self, offset):
        """Read qword from IDB"""
        self.ret = ida_bytes.get_qword(offset)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_name(self, offset):
        """Get name for offset from IDB"""
        self.ret = idc.get_name(offset, ida_name.GN_VISIBLE)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_segment_name(self, offset):
        """Get segment name for offset from IDB"""
        self.ret = idc.get_segm_name(offset)
        return self.ret

    @QtCore.pyqtSlot(object)
    def is_writable(self, offset):
        """Determine if memory address is writable"""
        self.ret = bool(idaapi.getseg(offset).perm & idaapi.SEGPERM_WRITE)
        return self.ret

    @QtCore.pyqtSlot(object)
    def resolve_address(self, offset):
        """Get module/symbol name for address"""
        symbol = self.get_name(offset)
        module = self.get_segment_name(offset)

        if not module and  "_" in symbol:
            # No module name for the segment, try to determine from the symbol name
            symbol_split = symbol.split("_")

            # Given a symbol, i.e. ws2_32_WSAStartup, can we find ws2_32.dll in the list of segments?
            for segment in idautils.Segments():
                segment_name = idc.get_segm_name(segment).lower()

                if segment_name.startswith(symbol_split[0].lower()):
                    new_name = ""
                    for i in range(0, len(symbol_split)):
                        new_name = "{}.dll".format("_".join(symbol_split[0:i]))
                        if new_name == segment_name:
                            break

                    if new_name == segment_name:
                        module = new_name
                        break

        # Still nothing?!
        if not module and  "_" in symbol:
            symbol_split = symbol.split("_")

            j = 1
            if symbol_split[0] == "ws2":
                j += 1
                module = "{}.dll".format("_".join(symbol_split[0:j]))
            else:
                module = "{}.dll".format(symbol_split[0])

        # Strip module name from symbol name
        if module:
            module_name = module.split(".")[0].lower()

            if symbol[:len(module_name)].lower().startswith(module_name):
                symbol = symbol[len(module_name) + 1:]

        if not symbol:
            symbol = "{:x}".format(offset)

        self.ret = (module, symbol)
        return self.ret

    @QtCore.pyqtSlot(object)
    def get_label(self, offset):
        """Get label for offest from IDB"""
        self.ret = idc.GetDisasm(offset).replace("extrn ", "").split(":")[0]
        return self.ret

    @QtCore.pyqtSlot(object, object)
    def make_string(self, offset, size):
        """Convert range to byte string in IDB"""
        self.ret = idc.create_strlit(offset, offset + size)
        return self.ret

    @QtCore.pyqtSlot(object, object, str, str, bytes)
    def make_segment(self, offset, size, class_name="DATA", name="pe_map", data=None):
        """Create a new section in the IDB"""
        idc.AddSeg(offset, offset + size, 0, 1, 0, idaapi.scPub)
        idc.RenameSeg(offset, str(name))
        idc.SetSegClass(offset, str(class_name))
        #idc.SegAlign(offset, idc.saRelPara)
        if data:
            idaapi.patch_many_bytes(offset, bytes(data))

        self.ret = None
        return self.ret

    @QtCore.pyqtSlot(object)
    def make_qword(self, offset):
        """Create a qword at the given offset in the IDB"""
        self.ret = idaapi.create_qword(offset, 8)
        return self.ret

    @QtCore.pyqtSlot(object)
    def make_dword(self, offset):
        """Create a dword at the given offset in the IDB"""
        self.ret = idaapi.create_dword(offset, 4)
        return self.ret

    @QtCore.pyqtSlot(object)
    def make_word(self, offset):
        """Create a word at the given offset in the IDB"""
        self.ret = idaapi.create_word(offset, 2)
        return self.ret

    @QtCore.pyqtSlot(object)
    def make_byte(self, offset, size=1):
        """Create a byte at the given offset in the IDB"""
        self.ret = idaapi.create_byte(offset, size)
        return self.ret

    @QtCore.pyqtSlot(object, str)
    def make_comment(self, offset, comment):
        """Create a comment at the given offset in the IDB"""
        #self.ret = idc.MakeComm(offset, comment)
        return self.ret

    @QtCore.pyqtSlot(object, str, int)
    def make_name(self, offset, name, flags=0):
        """Name the given offset in the IDB"""
        self.ret = idc.set_name(offset, str(name), idc.SN_NOCHECK | idc.SN_NOWARN | 0x800)
        return self.ret

    @QtCore.pyqtSlot()
    def get_names(self):
        self.ret = list(idautils.Names())
        return self.ret

    @QtCore.pyqtSlot(object, object, object, object)
    def find_iat_ptrs(self, pe, image_base, size, get_word):
        """Find all likely IAT pointers"""
        iat_ptrs = []

        next_offset = image_base

        while next_offset < image_base + size:
            offset = next_offset
            next_offset = ida_bytes.next_addr(offset)

            # Attempt to read the current instruction's effective memory address operand (if present)
            mnem = idc.print_insn_mnem(offset).lower()
            ptr = 0

            if mnem in ["call", "push", "jmp"]:
                if idc.get_operand_type(offset, 0) == idc.o_mem:
                    # Get memory offset for branch instructions
                    ptr = idc.get_operand_value(offset, 0)
            elif mnem in ["mov", "lea"]:
                if idc.get_operand_type(offset, 0) == idc.o_reg and idc.get_operand_type(offset, 1) == idc.o_mem:
                    # Get memory offset for mov/lea instructions
                    ptr = idc.get_operand_value(offset, 1)

            # Does the instruction's memory address operand seem somewhat valid?!
            if ptr < 0x1000:
                continue

            # Resolve pointer from memory operand
            iat_offset = get_word(ptr)

            # Ignore offset if it is in our image
            if image_base <= iat_offset <= image_base + size:
                continue

            # Get module and API name for offset
            module, api = self.resolve_address(iat_offset)

            # Ignore the offset if it is in a debug segment or stack etc
            if api and module and module.endswith(".dll"):
                if not iat_offset in iat_ptrs:
                    # Add IAT offset, address to patch, module name and API name to list
                    iat_ptrs.append((iat_offset, offset + idc.get_item_size(offset) - 4, module, api))

        self.ret = iat_ptrs
        return self.ret

    @QtCore.pyqtSlot(object)
    def find_pe(self, cursor=False):
        """Search IDB for possible MZ/PE headers"""
        info = idaapi.get_inf_structure()

        mz_headers = []

        # Check current cursor for MZ/PE?
        if cursor:
            # Get IDA cursor address
            addr = idc.here()

            # Get segment and end address
            s = idaapi.getseg(addr)
            e = idc.get_segm_end(addr)

            # Check for MZ magic
            if ida_bytes.get_word(addr) == 0x5a4d:
                # Ensure the PE header is in the segment
                e_lfanew = ida_bytes.get_dword(addr + 0x3c)

                if addr + e_lfanew + 1 < e:
                    # Check for PE magic
                    if ida_bytes.get_word(addr + e_lfanew) == 0x4550:
                        # Found possible MZ/PE header
                        mz_headers.append([addr, idc.get_segm_name(addr), info.is_64bit()])

            self.ret = mz_headers
            return self.ret

        # Search all segments
        for seg in idautils.Segments():
            s = idc.get_segm_start(seg)
            e = idc.get_segm_end(seg)
            addr = s

            while True:
                # Find first byte of MZ header
                addr = ida_bytes.find_byte(addr, e-addr, 0x4d, 0)

                if addr == ida_idaapi.BADADDR or addr >= e:
                    break

                # Check for MZ magic
                if ida_bytes.get_word(addr) == 0x5a4d:
                    # Ensure the PE header is in the segment
                    e_lfanew = ida_bytes.get_dword(addr + 0x3c)

                    if addr + e_lfanew + 1 < e:
                        # Check for PE magic
                        if ida_bytes.get_word(addr + e_lfanew) == 0x4550:
                            # Found possible MZ/PE header
                            mz_headers.append([addr, idc.get_segm_name(s), info.is_64bit()])

                # Resume search from next address
                addr += 1

        self.ret = mz_headers
        return self.ret

class pe_tree_plugin_t(idaapi.plugin_t):
    """Main IDAPro plugin class for PE Tree"""
    wanted_name = pe_tree.info.__title__
    comment = pe_tree.info.__title__
    version = pe_tree.info.__version__
    website = pe_tree.info.__url__
    help = pe_tree.info.__title__
    wanted_hotkey = ""
    flags = 0

    def __init__(self):
        self.pe_tree_form = None

    def init(self):
        """Initialise PE Tree IDA plugin"""
        return ida_idaapi.PLUGIN_KEEP

    def term(self):
        """Terminate PE Tree IDA plugin"""
        # Wait for plugin threads to complete
        if self.pe_tree_form:
            self.pe_tree_form.wait_for_threads()

    def run(self, arg):
        """Run PE Tree IDA plugin"""
        # Get form and widget
        form = idaapi.create_empty_widget(self.comment)
        widget = sip.wrapinstance(int(form), QtWidgets.QWidget)

        runtime = IDARuntime(form, {})

        self.pe_tree_form = pe_tree.form.PETreeForm(widget, None, runtime)

        # Try to find the IDA input file
        filename = idaapi.get_input_file_path()

        if filename:
            if not os.path.isfile(filename):
                filename = os.path.basename(filename)

                if not os.path.isfile(filename):
                    filename = runtime.ask_file(os.path.basename(filename), "Where is the input file?")

            if os.path.isfile(filename):
                # Map input file
                self.pe_tree_form.map_pe(image_base=idaapi.get_imagebase(), filename=filename)

        self.pe_tree_form.treeview.setVisible(True)
        self.pe_tree_form.show()

def PLUGIN_ENTRY():
    """PE Tree IDAPython plugin entry-point"""
    return pe_tree_plugin_t()

def main():
    """PE Tree IDAPython script entry-point"""
    PLUGIN_ENTRY().run("")

if __name__ == "__main__":
    main()


repository: herosi/CTO
file: cto_func_lister_plugin.py
description: Call Tree Overviewer
comment: CTO Function Lister
created_at: 2021-10-06 05:45:55+00:00
pushed_at: 2025-04-02 01:35:51+00:00
url:
https://github.com/herosi/CTO/blob/f6c34bcd3ac5f83d0c5e2d991537f887d9d78201/cto_func_lister_plugin.py

content:
import ida_idaapi
import ida_kernwin
import ida_funcs

import os
import sys
import codecs

ida_idaapi.require("cto")
ida_idaapi.require("cto.cto_base")
ida_idaapi.require("cto.cto_func_lister")
ida_idaapi.require("cto.icon")
ida_idaapi.require("cto.syncdata")
ida_idaapi.require("cto.qtutils")


# for IDA 7.4 or earlier
try:
    g_flags = ida_idaapi.PLUGIN_MULTI
except AttributeError:
    g_flags = ida_idaapi.PLUGIN_DRAW

# for IDA 7.4 or earlier
try:
    g_obj = ida_idaapi.plugmod_t
except AttributeError:
    g_obj = object

g_plugmod_flag = False
if g_flags != ida_idaapi.PLUGIN_DRAW and g_obj != object:
    g_plugmod_flag = True


class cto_func_lister_plugin_t(ida_idaapi.plugin_t):
    flags = g_flags
    comment = "CTO Function Lister"
    toolbar_displayed_name = cto.cto_base.cto_base.orig_title
    toolbar_name = toolbar_displayed_name + 'Toolbar'
    wanted_name = comment
    wanted_hotkey = "Alt-Shift-F"
    help = "Press '" + wanted_hotkey + "' to display the " + comment + " widget."
    
    action_name = "cto_func_lister:execute"
    menu_path = "Edit/Plugins/"
    icon = cto.icon.icon_handler(cto.icon.g_icon_data_ascii, True)
    icon_data = icon.icon_data
    icon_data_dark = icon.icon_bg_change(icon_data, True, True)
    if icon_data_dark is None:
        icon_data_dark = icon_data
    act_icon = ida_kernwin.load_custom_icon(data=icon_data, format="png")
    act_icon_dark = ida_kernwin.load_custom_icon(data=icon_data_dark, format="png")
    
    class exec_from_toolbar(ida_kernwin.action_handler_t):
        def __init__(self, plugin):
            ida_kernwin.action_handler_t.__init__(self)
            import weakref
            self.v = weakref.ref(plugin)
        
        def activate(self, ctx):
            self.v().plugin_mod.run(None)
            
        def update(self, ctx):
            return ida_kernwin.AST_ENABLE_ALWAYS
    
    def init(self):
        ida_kernwin.msg("############### %s (%s) ###############%s" % (self.wanted_name, self.comment, os.linesep))
        ida_kernwin.msg("%s%s" % (self.help, os.linesep))

        # attach to menu
        self.g = None
        ida_kernwin.attach_action_to_menu(
            self.menu_path,
            self.action_name,
            ida_kernwin.SETMENU_APP)

        # attach to toolbar
        ida_kernwin.register_action(
            ida_kernwin.action_desc_t(
            self.action_name,
            self.comment,
            self.exec_from_toolbar(self),
            None,
            self.wanted_name,
            self.act_icon))
        
        # Insert the action in a toolbar
        ida_kernwin.create_toolbar(self.toolbar_name, self.toolbar_displayed_name)
        ida_kernwin.attach_action_to_toolbar(self.toolbar_name, self.action_name)
        
        # install ui hook to enable toolbar later
        self.ph = cto.qtutils.enable_toolbar_t(self.toolbar_name)
        
        # Check if IDA is darkmode or not.
        # It might fail if the mode is darcula or similar themes
        # because the window color is the same as the default theme color.
        # However, it can still distinguish the default and the dark mode.
        if cto.cto_base.cto_base.is_dark_mode_with_main():
            ida_kernwin.update_action_icon(self.action_name, self.act_icon_dark)
            ida_kernwin.update_action_icon(self.menu_path + self.wanted_name, self.act_icon_dark)
            
        r = self.flags
        self.plugin_mod = cfl_plugmod_t()
        if g_plugmod_flag:
            r = self.plugin_mod
        return r

    # for old IDA til 7.6
    def run(self, arg):
        self.plugin_mod.run(arg)
        
    # for old IDA til 7.6
    def term(self):
        self.plugin_mod.term()

    @staticmethod
    class register_icon(ida_kernwin.UI_Hooks):
        def updated_actions(self):
            if ida_kernwin.update_action_icon(cto_func_lister_plugin_t.menu_path + cto_func_lister_plugin_t.wanted_name, cto_func_lister_plugin_t.act_icon_dark):
                # unhook this if it's successful
                self.unhook()


class cfl_plugmod_t(g_obj):
    toolbar_name = cto_func_lister_plugin_t.toolbar_name
    menu_path = cto_func_lister_plugin_t.menu_path
    action_name = cto_func_lister_plugin_t.action_name
    act_icon = cto_func_lister_plugin_t.act_icon
    wanted_name = cto_func_lister_plugin_t.wanted_name
    comment = cto_func_lister_plugin_t.comment
    help = cto_func_lister_plugin_t.help
        
    def __init__(self):
        g_obj.__init__(self)
        self.g = None
            
    def __del__(self):
        self.term()
        
    def run(self, arg):
        self.exec_cto_func_lister()
        
    def term(self):
        if self.g:
            self.g.Close(0)
        if hasattr(self.g, "sd"):
            self.g.sd.close()
            
        ida_kernwin.free_custom_icon(self.act_icon)
        ida_kernwin.detach_action_from_menu(self.menu_path, self.action_name)
        
        ida_kernwin.detach_action_from_toolbar(self.toolbar_name, self.action_name)
        ida_kernwin.delete_toolbar(self.toolbar_name)
        
        ida_kernwin.unregister_action(self.action_name)
        
        if hasattr(sys.modules["__main__"], "g_cto_func_lister"):
            delattr(sys.modules["__main__"], "g_cto_func_lister")
        if "g_cto_func_lister" in globals():
            global g_cto_func_lister
            del g_cto_func_lister
        self.g = None
        
    def exec_cto_func_lister(self):
        global g_cto_func_lister

        # for degub mode handling
        debug = False
        if "g_debug" in globals() and g_debug:
            debug = True
        elif hasattr(sys.modules["__main__"], "g_debug") and sys.modules["__main__"].g_debug:
            debug = True

        # for the first message when this plugin is launched by a user by pressing shortcut key or going to menu.
        if 'g_cto_func_lister' not in globals():
            ida_kernwin.msg("Launching %s (%s) ...%s" % (self.wanted_name, self.comment, os.linesep))
            ida_kernwin.msg("For the first execution, %s will analyze all functions to build the call tree. Please wait for a while.%s" % (self.wanted_name, os.linesep))
        else:
            ida_kernwin.msg("Reloading %s.%s" % (self.wanted_name, os.linesep))
            
        if self.g:
            # save several important data with pickle and closing hooks...
            self.g.Close(0)
        
        # reload the main modules
        ida_idaapi.require("cto.cto_func_lister")

        # get sync data on a global variable
        sd = cto.syncdata.sync_data()
        sync_data = sd.get()
        # execute the main function
        self.g = cto.cto_func_lister.exec_cto_function_lister(cto_data=sync_data, debug=debug)
        self.g.__dict__["sd"] = sd
        if sync_data is None:
            self.g.sd.set(self.g.cto_data)
            
        # install ui hook to enable toolbar later
        self.ph = cto.qtutils.enable_toolbar_t(self.toolbar_name)
        
        # show the messages after launching
        if 'g_cto_func_lister' not in globals():
            ida_kernwin.msg("Launched %s.%s" % (self.wanted_name, os.linesep))
        else:
            ida_kernwin.msg("Reloaded %s.%s" % (self.wanted_name, os.linesep))
            
        # put the instance in the global variables
        g_cto_func_lister = self.g
        if not hasattr(sys.modules["__main__"], "g_cto_func_lister"):
            setattr(sys.modules["__main__"], "g_cto_func_lister", self.g)
        else:
            sys.modules["__main__"].g_cto_func_lister = self.g


def PLUGIN_ENTRY():
    return cto_func_lister_plugin_t()

"""
def main():
    global g_cto_func_lister
    g_cto_func_lister = cto_func_lister.exec_cto_function_lister()

if __name__ == '__main__':
    main()
"""

ri = cto_func_lister_plugin_t.register_icon()
ri.hook()


repository: patois/HRDevHelper
file: hrdevhelper.py
description: HexRays ctree visualization plugin
url: https://github.com/patois/HRDevHelper/blob/042f8e083068ad27c2fa2e4903ee2f4983d886a7/hrdevhelper.py
created_at: 2019-05-23 17:54:25+00:00
pushed_at: 2024-09-06 16:14:25+00:00
content:
import ida_idaapi
import ida_hexrays
import ida_kernwin
import hrdh.config as cfg
import hrdh.handler as handler
import hrdh.contextview as cview
import hrdh.graph as graph

__author__ = "https://github.com/patois/"

# -----------------------------------------------------------------------
class HRDevHelper(ida_idaapi.plugin_t):
    comment = ""
    help = ""
    wanted_name = cfg.PLUGIN_NAME
    wanted_hotkey = ""
    flags = ida_idaapi.PLUGIN_DRAW
    if not cfg.DEBUG:
        flags |= ida_idaapi.PLUGIN_HIDE
    act_show_ctree = "Show ctree"
    act_show_sub_tree = "Show sub-tree"
    act_show_context = "Show context"
    config = None

    @staticmethod
    def get_action_name(desc):
        return "%s:%s" % (cfg.PLUGIN_NAME, desc)

    def _register_action(self, hotkey, desc):
        actname = HRDevHelper.get_action_name(desc)
        ida_kernwin.msg("%s -> %s\n" % (hotkey, actname))
        if ida_kernwin.register_action(ida_kernwin.action_desc_t(
            actname,
            desc,
            handler.hotkey_handler_t(HRDevHelper.config),
            hotkey,
            None,
            -1)):
            self._registered_actions[actname] = (desc, hotkey)
        else:
            ida_kernwin.warning("%s: failed to register action" % cfg.PLUGIN_NAME)

    def _install(self):
        self._register_action(HRDevHelper.config["show_tree"], HRDevHelper.act_show_ctree)
        self._register_action(HRDevHelper.config["show_subtree"], HRDevHelper.act_show_sub_tree)
        self._register_action(HRDevHelper.config["show_context"], HRDevHelper.act_show_context)
        self.ui_hooks = handler.ui_event_handler_t(self._registered_actions, HRDevHelper.config)
        self.ui_hooks.hook()
        return True

    def _uninstall(self):
        if self.installed:
            self.ui_hooks.unhook()
            for desc in self._registered_actions:
                ida_kernwin.unregister_action(desc)

    def init(self):
        self.installed = False
        self._registered_actions = {}
        result = ida_idaapi.PLUGIN_SKIP
        if ida_hexrays.init_hexrays_plugin():
            try:
                HRDevHelper.config = cfg.load_cfg()
            except:
                ida_kernwin.warning(("%s failed parsing %s.\n"
                    "If fixing this config file manually doesn't help, please delete the file and re-run the plugin.\n\n"
                    "The plugin will now terminate." % (
                        cfg.PLUGIN_NAME,
                        cfg.get_cfg_filename())))
            else:
                self.installed = self._install()
                result = ida_idaapi.PLUGIN_KEEP 
        return result

    def run(self, arg):
        if cfg.DEBUG:
            print(HRDevHelper.config)

    def term(self):
        self._uninstall()

# -----------------------------------------------------------------------
def PLUGIN_ENTRY():   
    return HRDevHelper()


repository: FelixBer/FindFunc
file: findfuncmain.py
url: https://github.com/FelixBer/FindFunc/blob/0c00b9d8bda04abd845bf9186b5ee2c16364ed17/findfuncmain.py
comment: Function Finder and Advanced copying of instruction bytes
created_at: 2022-03-15 10:44:51+00:00
pushed_at: 2024-09-15 20:05:26+00:00
description:
FindFunc is an IDA Pro plugin to find code functions that contain a certain assembly or byte pattern, reference a certain name or string, or conform to various other constraints.

content:
import sys

from PyQt5.QtWidgets import QApplication, QMessageBox, QVBoxLayout

from findfunc import findfunc_gui

inida = True
try:
    # from idaapi import PluginForm
    import idc
    import idaapi
    import idautils
    import ida_bytes
    import ida_ua
    import ida_pro
    from idaapi import PluginForm
except:
    inida = False
    print("not in ida")

if inida:
    from findfunc.advanced_copy import copy_all_bytes, copy_bytes_no_imm, copy_only_opcodes, copy_only_disasm, copy_opcodes_and_imm

__AUTHOR__ = 'feber'

PLUGIN_NAME = "FindFunc (x86/x64)"
PLUGIN_HOTKEY = 'ctrl+alt+f'
VERSION = '1.4'
WINDOWTITLE = f'{PLUGIN_NAME} {VERSION}'
INFOSTR = f'For usage see: ' '<a href="https://github.com/FelixBer/FindFunc">https://github.com/FelixBer/FindFunc</a>'

# this is executed when running a script rather than plugin
if __name__ == "__main__":
    if inida and not idaapi.get_input_file_path():
        QMessageBox.information(None, "No File", "Please load a file in IDA first, then run script again.")
    else:
        app = QApplication(sys.argv) if not inida else None  # if we run outside IDA for testing
        tabwid = findfunc_gui.TabWid(False)
        tabwid.setInfoString(INFOSTR)
        tabwid.setWindowTitle(WINDOWTITLE)
        tabwid.show()
        if app:
            sys.exit(app.exec_())


# plugin stuff

def PLUGIN_ENTRY():
    return FindFunc()


class FindFunc(idaapi.plugin_t):
    """
    Main Plugin Class
    """
    flags = 0  # idaapi.PLUGIN_PROC  # | idaapi.PLUGIN_FIX #| idaapi.PLUGIN_HIDE
    comment = "Function Finder and Advanced copying of instruction bytes"
    help = f"Edit->Plugin->FindFunc or {PLUGIN_HOTKEY}. Also: disasm->rightclick->copy all|opcode|noimm"
    wanted_name = PLUGIN_NAME
    wanted_hotkey = PLUGIN_HOTKEY

    ACTION_COPY_BYTES = "feber:copy_bytes"
    ACTION_COPY_OPC = "feber:copy_opc"
    ACTION_COPY_OPC_IMM = "feber:copy_opc_imm"
    ACTION_COPY_NO_IMM = "feber:copy_no_imm"
    ACTION_COPY_DISASM = "feber:copy_disasm"
    ACTION_FFOPEN = "feber:ffopen"

    def __init__(self):
        super().__init__()
        self.hooks = ACUiHook()
        self.maintabwidgtet = None

    def init(self):
        # see advanced_copy for details
        action_desc = idaapi.action_desc_t(
            self.ACTION_COPY_BYTES,
            "copy bytes",
            ACActionHandler(copy_all_bytes),
            "ctrl+alt+b",  # hotkey
            "copy all selected bytes as hex",
            31
        )
        assert idaapi.register_action(action_desc), "Action registration failed"
        action_desc = idaapi.action_desc_t(
            self.ACTION_COPY_OPC,
            "copy opcodes",
            ACActionHandler(copy_only_opcodes),
            "ctrl+alt+o",  # hotkey
            "copy selected opcodes as hex, mask out non-opcode bytes",
            31
        )
        assert idaapi.register_action(action_desc), "Action registration failed"
        action_desc = idaapi.action_desc_t(
            self.ACTION_COPY_OPC_IMM,
            "copy opcodes and immediates",
            ACActionHandler(copy_opcodes_and_imm),
            "ctrl+alt+o+i",  # hotkey
            "copy instruction bytes, mask everything but opcodes + immediates",
            31
        )
        assert idaapi.register_action(action_desc), "Action registration failed"
        action_desc = idaapi.action_desc_t(
            self.ACTION_COPY_NO_IMM,
            "copy without immediates",
            ACActionHandler(copy_bytes_no_imm),
            "ctrl+alt+i",  # hotkey
            "copy instruction bytes, mask out all immediates",
            31
        )
        assert idaapi.register_action(action_desc), "Action registration failed"
        action_desc = idaapi.action_desc_t(
            self.ACTION_COPY_DISASM,
            "copy disasm",
            ACActionHandler(copy_only_disasm),
            "ctrl+alt+d",  # hotkey
            "copy disasm lines only",
            31
        )
        assert idaapi.register_action(action_desc), "Action registration failed"
        action_desc = idaapi.action_desc_t(
            self.ACTION_FFOPEN,
            "FindFunc",
            ACActionHandler(open_form),
            "ctrl+alt+f",  # hotkey
            "",
            -1
        )
        assert idaapi.register_action(action_desc), "Action registration failed"

        idaapi.attach_action_to_menu(
            'View/',
            self.ACTION_FFOPEN,
            idaapi.SETMENU_APP)

        self.hooks.hook()

        idaapi.msg("%s %s by %s loaded\n" % (self.wanted_name, VERSION, __AUTHOR__))
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        Edit->Plugins->... or hotkey
        """
        open_form()

    def term(self):
        self.hooks.unhook()
        idaapi.unregister_action(self.ACTION_COPY_BYTES)
        idaapi.unregister_action(self.ACTION_COPY_OPC)
        idaapi.unregister_action(self.ACTION_COPY_OPC_IMM)
        idaapi.unregister_action(self.ACTION_COPY_NO_IMM)
        idaapi.unregister_action(self.ACTION_COPY_DISASM)
        idaapi.unregister_action(self.ACTION_FFOPEN)
        global cursession
        global lastsavedsession
        if cursession and cursession != lastsavedsession:
            reply = QMessageBox.question(None, "Save Session", "Your FindFunc session has not been saved. Save now?",
                                             QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.Yes:
                findfunc_gui.TabWid.save_as_session(cursession)
                lastsavedsession = cursession


# plugin helper stuff

cursession = ""
lastsavedsession = ""


def open_form():
    """
    open the same form, no matter how the plugin is launched
    """
    global ffform
    try:
        ffform
    except Exception:
        ffform = FunctionsListForm_t()
    ffform.Show()


class FunctionsListForm_t(PluginForm):
    """
    wrapper required for docking
    """
    def OnCreate(self, form):
        self.parent = self.FormToPyQtWidget(form)
        self.mtw = findfunc_gui.TabWid(True)
        self.mtw.setInfoString(INFOSTR)
        self.mtw.setWindowTitle(WINDOWTITLE)
        global cursession
        if cursession:
            self.mtw.clearAll()
            self.mtw.load_session_from_text(cursession)
            cursession = ""
        layout = QVBoxLayout()
        layout.addWidget(self.mtw)
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        self.parent.setLayout(layout)

    def OnClose(self, form):
        global cursession
        global lastsavedsession
        cursession = self.mtw.session_to_text()
        lastsavedsession = self.mtw.lastsessionsaved

    def Show(self):
        return PluginForm.Show(self, WINDOWTITLE, options=PluginForm.WOPN_PERSIST)


class ACActionHandler(idaapi.action_handler_t):
    """
    Action handling helper
    """
    def __init__(self, action_function):
        idaapi.action_handler_t.__init__(self)
        self.action_function = action_function

    def activate(self, ctx):
        self.action_function()
        return 1

    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS


class ACUiHook(idaapi.UI_Hooks):
    """
    Show context menus in disasm widgets
    """
    def finish_populating_widget_popup(self, widget, popup):
        """
        Right click menu is about to be shown
        """
        form_type = idaapi.get_widget_type(widget)

        if form_type == idaapi.BWN_DISASMS:
            idaapi.attach_action_to_popup(
                widget,
                popup,
                FindFunc.ACTION_COPY_BYTES,
                # "copy all bytes",
                # idaapi.SETMENU_APP
            )
            idaapi.attach_action_to_popup(
                widget,
                popup,
                FindFunc.ACTION_COPY_OPC,
            )
            idaapi.attach_action_to_popup(
                widget,
                popup,
                FindFunc.ACTION_COPY_OPC_IMM,
            )
            idaapi.attach_action_to_popup(
                widget,
                popup,
                FindFunc.ACTION_COPY_NO_IMM,
            )
            idaapi.attach_action_to_popup(
                widget,
                popup,
                FindFunc.ACTION_COPY_DISASM,
            )
        return 0


repository: deactivated/idaenv
file: idaenv/manager.py
description: IDA Pro plugin manager
created_at: 2018-07-03 17:35:16+00:00
pushed_at: 2023-01-23 02:54:11+00:00
url:
https://github.com/deactivated/idaenv/blob/93b6167e0cd129b5782453455984e306b29a9c6d/idaenv/manager.py

content:
from __future__ import print_function

import re
import ast
import os
import hashlib

from . import entrypoints
from .utils import get_virtualenv_path, get_default_ida_usr


MODULE_TYPES = {"plugins", "loaders", "procs"}


PLUGIN_TEMPLATE = """
# EntryPointInfo(%(dist)r, %(group)r, %(name)r, %(module)r, %(attr)r)

from %(module)s import %(attr)s

def PLUGIN_ENTRY():
    return %(attr)s()
"""

PROC_TEMPLATE = """
# EntryPointInfo(%(dist)r, %(group)r, %(name)r, %(module)r, %(attr)r)

from %(module)s import *
"""


LOADER_TEMPLATE = """
# EntryPointInfo(%(dist)r, %(group)r, %(name)r, %(module)r, %(attr)r)

from %(module)s import *
"""


class PluginManager(object):
    """
    IDAPython plugin manager.
    """

    template_map = {
        "plugins": PLUGIN_TEMPLATE,
        "procs": PROC_TEMPLATE,
        "loaders": LOADER_TEMPLATE,
    }

    wrapper_rx = r"^[a-zA-Z][a-zA-Z0-9_]*_[0-9a-fA-F]+\.py$"

    def __init__(self, user_dir):
        self.user_dir = user_dir
        self.initialize_user_dir(self.user_dir)

        # XXX: Doing this here is a bit of a hack; pkg_resources needs to be
        # re-initialized whenever a module is installed or removed.
        entrypoints.refresh_entrypoint_caches()

    def initialize_user_dir(self, user_dir):
        if not user_dir:
            raise ValueError("Invalid user directory.")

        if not os.path.isdir(user_dir):
            os.makedirs(user_dir)

        for module_type in MODULE_TYPES:
            subdir_path = self.wrapper_dir(module_type)
            if not os.path.isdir(subdir_path):
                os.mkdir(subdir_path)

    def update_plugins(self):
        "Update the list of installed IDA plugins and wrapper files."
        # Update module wrappers
        for module_type in MODULE_TYPES:
            plan = self.plan_update(module_type)
            self.execute_update(module_type, plan)

    def find_module_wrapper(self, module_type, module_name):
        "Locate a module wrapper by name."
        wrapper_dir = self.wrapper_dir(module_type)
        wrappers = self.find_wrappers(wrapper_dir)
        for ep, wrapper_path in wrappers:
            if "%s.%s" % (ep.dist, ep.name) == module_name:
                return (ep, wrapper_path)

    def plan_delete(self, wrappers):
        return {"create": [], "delete": list(wrappers)}

    def plan_update(self, module_type):
        "Plan actions for synchronization."
        if module_type not in MODULE_TYPES:
            raise ValueError("Invalid module type: %r" % module_type)

        # Scan installed modules
        cur_modules = self.find_installed_modules(module_type)

        # Scan installed wrappers
        wrapper_dir = self.wrapper_dir(module_type)
        wrappers = self.find_wrappers(wrapper_dir)
        wrapper_set = set(ep for ep, _ in wrappers)

        # Active modules don't need a change
        active_modules = [ep for ep in cur_modules if ep in wrapper_set]

        # Delete wrappers for uninstalled modules
        to_delete = [(ep, path) for ep, path in wrappers if ep not in cur_modules]

        # Create wrappers for new modules
        to_create = [ep for ep in cur_modules if ep not in wrapper_set]

        return {"active": active_modules, "delete": to_delete, "create": to_create}

    def execute_update(self, module_type, changes):
        # Delete uninstalled modules
        for ep, wrapper_path in changes["delete"]:
            os.remove(wrapper_path)

        # Create new wrappers
        for ep in changes["create"]:
            self.write_entry_point_wrapper(module_type, ep)

    def write_entry_point_wrapper(self, module_type, ep_info):
        "Create a wrapper file for IDA."
        wrapper = self.template_map[module_type] % ep_info._asdict()

        dst_path = os.path.join(
            self.wrapper_dir(module_type), self.wrapper_name(ep_info)
        )
        print("Writing wrapper to %r..." % dst_path)
        with open(dst_path, "w") as wf:
            wf.write(wrapper)

    def entry_point_name(self, module_type):
        return "idapython_" + module_type

    def find_installed_modules(self, module_type):
        entry_point_group = self.entry_point_name(module_type)
        return set(entrypoints.iter_entry_point_info(entry_point_group))

    def wrapper_dir(self, module_type):
        "Return path to wrappers for a given module type."
        return os.path.join(self.user_dir, module_type)

    def find_wrappers(self, subdir):
        "Return a list of wrappers in a directory."
        wrappers = []
        for name in os.listdir(subdir):
            path = os.path.join(subdir, name)
            if os.path.isfile(path) and re.match(self.wrapper_rx, name):
                info = self.read_wrapper_info(path)
                if info:
                    wrappers.append((info, path))
        return wrappers

    def read_wrapper_info(self, path):
        """
        Extract the entry point loaded by a wrapper.
        """
        with open(path, "r") as f:
            # Wrappers generated by this manager will be small.
            content = f.read(4096)

        m = re.search(r"EntryPointInfo(\(.*?\))", content)
        if m:
            ep = ast.literal_eval(m.group(1))
            if (
                isinstance(ep, tuple)
                and len(ep) == 5
                and all(isinstance(elt, str) for elt in ep)
            ):
                return entrypoints.EntryPointInfo(*ep)

    def wrapper_name(self, ep_info):
        def clean(s):
            s = re.sub(r"(?<=[a-z])[A-Z]", lambda m: "_" + m.group(0).lower(), s)
            s = s.lower()
            s = re.sub(r"[^a-zA-Z0-9_]", "", s)
            s = s.strip("_")
            return s

        dist_part = clean(ep_info.dist[:15])
        name_part = clean(ep_info.name[:15])
        sha_part = hashlib.sha1("_".join(ep_info).encode("utf8")).hexdigest()[:6]
        return "%s_%s_%s.py" % (dist_part, name_part, sha_part)


def get_default_manager(require_venv=False):
    """
    Initialize a plugin manager based on the current environment.
    """
    cur_env = get_virtualenv_path()
    if cur_env:
        ida_path = os.path.join(cur_env, "ida")
    elif require_venv:
        raise RuntimeError("Not in virtual environment.")
    else:
        print("Warning: operating outside of a virtual environment.")
        ida_path = get_default_ida_usr()

    return PluginManager(ida_path)


repository: therealzoomgod/pySigMaker
file: pySigMaker.py
description: Port of IDA plugin SigMaker-x64 to IDAPython
wanted_name: pySigMaker
comment: Creates code signature patterns.
created_at: 2021-04-10 18:34:26+00:00
pushed_at: 2022-10-03 02:53:27+00:00
url:
https://github.com/therealzoomgod/pySigMaker/blob/2e455ac9f30742dcde425a3d992866b763f5b901/pySigMaker.py

content:
# -*- coding: utf-8 -*-

PLUGIN_VERSION = '0.1.50'

# If True the starting address will be current function when making a sig for functions.
# SigMaker-x64 behavior is to look for 5 or more references before adding function start
FUNC_START_EA = True

# Can be set via Gui, this is just for a fallback default
PLUGIN_HOTKEY = 'Ctrl-Alt-S'


"""
    pySigMaker:

    Ported by: zoomgod - unknowncheats.me

    IDAPython port for most of the origional compiled SigMaker-x64 IDA
    plugin with some minor changes, bug-fix and new GUI.

    Credits to the origional author/contributors of SigMaker-x64
    https://github.com/ajkhoury/SigMaker-x64

    See readme for IDA/Python requirements
"""

import sys, pickle, os, shutil

PLUGIN_DIR, PLUGIN_FILENAME = sys.argv[0].rsplit('/', 1)
HOTKEY_CONFLICT = False
SIGMAKER_X64_PLUGINS = []

if PLUGIN_DIR.find('plugins') == -1:
    PLUGIN_DIR = ''
else:
    if os.path.exists('%s/sigmaker.dll' % PLUGIN_DIR):
        SIGMAKER_X64_PLUGINS.append('sigmaker.dll')

    if os.path.exists('%s/sigmaker64.dll' % PLUGIN_DIR):
        SIGMAKER_X64_PLUGINS.append('sigmaker64.dll')

    HOTKEY_CONFLICT = len(SIGMAKER_X64_PLUGINS) > 0

try:
    import tkinter
    from enum import unique, IntEnum
except:
    print('Python 3.4 > required.')
    sys.exit(0)

# Gui
from PyQt5 import Qt, QtCore, QtGui, QtWidgets

import idc
import idaapi, ida_kernwin
from idaapi import BADADDR

# Ignore this, just for when I debug gui layout issues
GUI_DBG_ENABLED = False
try:
    from SigMakerDebug import QTDebugHelper
    GUI_DBG_ENABLED = True
except:
    pass

@unique
class QueryTypes(IntEnum):
    QUERY_FIRST     = 0     # Return 1st match
    QUERY_COUNT     = 1     # Return count
    QUERY_UNIQUE    = 2     # Return True/False

@unique
class PatternType(IntEnum):
    PT_INVALID      = -1
    PT_DIRECT       = 0
    PT_FUNCTION     = 1
    PT_REFERENCE    = 2

@unique
class SigType(IntEnum):
    SIG_IDA         = 0
    SIG_CODE        = 1
    SIG_OLLY        = 2

@unique
class SigSelect(IntEnum):
    OPT_LENGTH      = 0
    OPT_OPCODES     = 1
    OPT_WILDCARDS   = 2

@unique
class LogOptions(IntEnum):
    LOG_ERROR       = 0
    LOG_RESULT      = 1
    LOG_DEBUG       = 2


#
#
#  Utility functions
#
#

class QueryStruct:
    def __init__(self, pattern=b'', mask = b'', startea=BADADDR, endea=BADADDR):
        self.pattern = pattern
        self.mask = mask
        self.startea = startea
        self.endea = endea
        self.ea = BADADDR

        if self.startea == BADADDR:
            self.startea = idaapi.inf_get_min_ea()

        if self.endea == BADADDR:
            self.endea = idaapi.inf_get_max_ea()

def BinSearch(query) -> QueryStruct:
    """
    Searches for matching sequence of bytes based on a pattern and mask

    args:
        QueryStruct

    returns:
        QueryStruct with ea filled in
    """

    startea = query.startea
    if query.startea == BADADDR:
        query.startea = idaapi.inf_get_min_ea()

    endea = query.endea
    if query.startea == BADADDR:
        query.startea = idaapi.inf_get_max_ea()

    query.ea = idaapi.bin_search( query.startea, query.endea, query.pattern, query.mask,
        idaapi.BIN_SEARCH_FORWARD,
        idaapi.BIN_SEARCH_NOBREAK | idaapi.BIN_SEARCH_NOSHOW )

    return query

def MakeBin(ida_pattern, startea=BADADDR, endea=BADADDR) -> QueryStruct:
    """
        makeBin(ida_pattern)
        Returns QueryStruct with bin_search compatible pattern and mask from an IDA style pattern
    """

    patt = bytearray()
    mask = bytearray()

    for i in ida_pattern.split(' '):
        if i == '?':
            patt.append(0)
            mask.append(0)
        else:
            patt.append(int(i, 16))
            mask.append(1)

    return QueryStruct(bytes(patt), bytes(mask), startea, endea)

def BinQuery(sig, flag = QueryTypes.QUERY_FIRST, startea=None, endea = None):
    """
    Args:
        sig : IDA style pattern string
        flag: One of QueryTypes enum members

    Return types:
        flag == QUERY_FIRST  returns ea, search stops when matches == 1
        flag == QUERY_COUNT  returns int, full search
        flag == QUERY_UNIQUE returns boolean, search stops when matches > 1
    """

    query = MakeBin(sig)

    Result = []

    query = BinSearch(query)
    while query.ea != BADADDR:

        Result.append(query.ea)

        if flag == QueryTypes.QUERY_UNIQUE and len(Result) > 1:
            break

        if flag == QueryTypes.QUERY_FIRST:
            return Result[0]

        query.startea = query.ea + 1
        ea = BinSearch(query)

    if flag == QueryTypes.QUERY_UNIQUE:
        return len(Result) == 1
    elif flag == QueryTypes.QUERY_COUNT:
        return len(Result)
    elif flag == QueryTypes.QUERY_FIRST:
        return BADADDR

    raise ValueError('Invalid flag passed')

#
#
#  Pattern converters
#
#
def Ida2Code(sig) -> str:
    """
    Ida2Code(sig)

    Convert an IDA sig to code pattern and mask

    Arg:
        sig: IDA style sig

    Returns:
        string, string
    """

    mask = ''
    patt = ''

    for entry in sig.split(' '):
        if entry == '?':
            patt = patt + '\\x00'
            mask = mask + '?'
        else:
            patt = patt + '\\x%s' % entry
            mask = mask + 'x'

    return patt, mask

def Ida2Olly(sig) -> str:
    """
    Ida2Olly(sig)

    Convert an IDA sig to an Olly Debugger compatible sig

    Arg:
        sig: IDA style sig

    Return:
        string
    """

    pattern = []

    for entry in sig.split(' '):
        if entry == '?':
            pattern.append('??')
        else:
            pattern.append(entry)

    return " ".join(pattern)

def Code2Ida(patt, mask=None) -> str:
    """
    Code2Ida(sig)

    Convert an code style sig to an IDA sig

    Note:  When no mask is supplied any \x00 in pattern become a wildcards.

    Arg:
        sig : required, code style sig
        mask: optional

    Return:
        string
    """

    pattern = []
    p = []

    # convert binary string or regular string into a list of ints
    # Since \ is an escape character in Python have to check
    # for varying strings
    if not type(patt) is type(b''):
        if type(patt) is type('') and patt.find('\\') > -1:
            p = [ int('0x%s' % x, 16) for x in patt.split('\\x')[1:] ]
        else:
            return ''
    else:
        # binary string, can just convert to list
        p = list(patt)

    if mask and len(mask) != len(p):
        return ''

    for i in range(len(p)):
        if mask:
            if mask[i] == 'x':
                pattern.append('%02X' % p[i])
            else:
                pattern.append('?')
        elif p[i] > 0:
            pattern.append('%02X' % p[i])
        else:
            pattern.append('?')

    return ' '.join(pattern)

def GetIdaSig(sig, mask = None) -> str:
    """
    GetIdaSig(sig)

    Converts Olly or Code style sigs to an IDA style sigs

    Arg:
        sig : required, olly or code style sig
        mask: optional, valid only for code sigs

    Return:
        string
    """

    # Only a code sig should be byte string
    if type(sig) is type(b''):
        return Code2Ida(sig, mask)

    if sig.find(' ') > -1:

        # an olly sig without wildcards would be same as an ida sig so this is safe
        if sig.find(' ?? ') > -1:
            return sig.replace('??', '?')

        # Olly sig with no wildcards or already an ida sig
        return sig

    # Only supported type left is code sigs as a string
    return Code2Ida(sig, mask)

def GetSigType(sig) -> SigType:

    if type(sig) is type(b'') or sig.find('\\') > -1:
        return SigType.SIG_CODE

    if sig.find(' ') > -1:
        if sig.find(' ?? ') > -1:
            return SigType.SIG_OLLY
        return SigType.SIG_IDA

    return SigType.SIG_CODE

#
#
#  SigMaker
#
#
class SigCreateStruct:
    def __init__(self):
        self.sig = []
        self.dwOrigStartAddress = BADADDR   #ea at cursor when started
        self.dwStartAddress = BADADDR
        self.dwCurrentAddress = BADADDR
        self.bUnique = False
        self.iOpCount = 0
        self.eType = PatternType.PT_INVALID

class SigMaker:
    """
    Public methods:
        AutoFunction()
        AutoAddress()
    """

    def __init__(self, plugin):
        self.__plugin = plugin
        self.Sigs = []

    def _reset(self):
        self.Sigs = []

    def _addBytesToSig(self, sigIndex, ea, size):

        for i in range(0, size):
            b = idaapi.get_byte( ea + i )
            self.Sigs[sigIndex].sig.append('%02X' % b)

    def _addWildcards(self, sigIndex, count):
        for i in range(0, count):
            self.Sigs[sigIndex].sig.append('?')

    def _getCurrentOpcodeSize(self, cmd) -> (int, int):

        count = 0

        for i in range(0, idaapi.UA_MAXOP):

            count = i
            if cmd.ops[i].type == idaapi.o_void:
                return 0, count

            if cmd.ops[i].offb != 0:
                return cmd.ops[i].offb, count

        return 0, count

    def _matchOperands(self, ea) -> bool:

        if idaapi.get_first_dref_from(ea) != BADADDR:
            return False
        elif not self.__plugin.Settings.bOnlyReliable:
            if idaapi.get_first_fcref_from(ea) != BADADDR:
                return False
        elif idaapi.get_first_cref_from(ea) != BADADDR:
            return False

        return True

    def _addInsToSig(self, cmd, sigIndex):

        size, count = self._getCurrentOpcodeSize(cmd)

        if size == 0:
            self._addBytesToSig(sigIndex, cmd.ea, cmd.size)
            return
        else:
            self._addBytesToSig(sigIndex, cmd.ea, size)

        if self._matchOperands(cmd.ea):
            self._addBytesToSig(sigIndex, cmd.ea + size, cmd.size - size)
        else:
            self._addWildcards(sigIndex, cmd.size - size)

    def _addToSig(self, sigIndex) -> bool:

        cmd = idaapi.insn_t()
        cmd.size = 0

        sig = self.Sigs[sigIndex]

        if not idaapi.can_decode(sig.dwCurrentAddress):
            return False

        count = idaapi.decode_insn(cmd, sig.dwCurrentAddress)

        if count == 0 or cmd.size == 0:
            return False

        if cmd.size < 5:
            self._addBytesToSig(sigIndex, sig.dwCurrentAddress, cmd.size)
        else:
            self._addInsToSig(cmd, sigIndex)

        sig.dwCurrentAddress = sig.dwCurrentAddress + cmd.size
        sig.iOpCount = sig.iOpCount + 1

        self.Sigs[sigIndex] = sig

        return True

    def _haveUniqueSig(self) -> bool:
        for i in range(0, len(self.Sigs)):
            if self.Sigs[i].bUnique:
                return True
        return False

    def _addRefs(self, startea) -> bool:

        self.__plugin.log('Adding references', LogOptions.LOG_DEBUG)

        if idaapi.get_func_num(startea) != -1:
            sig = SigCreateStruct()
            sig.dwStartAddress = startea
            sig.dwCurrentAddress = startea
            sig.eType = PatternType.PT_DIRECT
            self.Sigs.append(sig)
            self.__plugin.log('Added direct reference 0x%X' % startea, LogOptions.LOG_DEBUG)

        eaCurrent = idaapi.get_first_cref_to(startea)
        while eaCurrent != BADADDR:

            if eaCurrent != startea:
                sig = SigCreateStruct()
                sig.dwStartAddress = eaCurrent
                sig.dwCurrentAddress = eaCurrent
                sig.eType = PatternType.PT_REFERENCE
                self.Sigs.append(sig)
                self.__plugin.log('Added reference 0x%X' % eaCurrent, LogOptions.LOG_DEBUG)

            if self.__plugin.Settings.maxRefs > 0 and len(self.Sigs) >= self.__plugin.Settings.maxRefs:
                break

            eaCurrent = idaapi.get_next_cref_to(startea, eaCurrent)

        if len(self.Sigs) < 5:

            self.__plugin.log('Not enough references were found (%i so far), trying the function.' % len(self.Sigs), LogOptions.LOG_DEBUG)

            func = idaapi.get_func(startea)

            if not func or func.start_ea == BADADDR:
                self.__plugin.log('Selected address not in a valid function.', LogOptions.LOG_ERROR)
                return False

            if func.start_ea != startea:

                eaCurrent = idaapi.get_first_cref_to(func.start_ea)

                while eaCurrent != BADADDR:

                    if eaCurrent != startea:
                        sig = SigCreateStruct()
                        sig.dwStartAddress = func.start_ea
                        sig.dwCurrentAddress = eaCurrent
                        sig.eType = PatternType.PT_FUNCTION
                        self.Sigs.append(sig)
                        self.__plugin.log('Added function 0x%X' % eaCurrent, LogOptions.LOG_DEBUG)

                    if self.__plugin.Settings.maxRefs > 0 and len(self.Sigs) >= self.__plugin.Settings.maxRefs:
                        break

                    eaCurrent = idaapi.get_next_cref_to(func.start_ea, eaCurrent)

        if not len(self.Sigs):
            self.__plugin.log('Automated signature generation failed, no references found.', LogOptions.LOG_ERROR)
            return False

        self.__plugin.log('Added %i references.' % len(self.Sigs), LogOptions.LOG_DEBUG)

        return True

    def _chooseSig(self) -> bool:

        max = 9999
        selected = -1

        for sigIndex in range(0, len(self.Sigs)):

            sig = self.Sigs[sigIndex]

            # drop wildcards off end of sig
            while sig.sig[-1] == '?':
                sig.sig = sig.sig[:-1]

            if sig.bUnique:

                sigLen = len(sig.sig)

                if self.__plugin.Settings.SigSelect == SigSelect.OPT_LENGTH:
                    if sigLen < max or (sig.eType == PatternType.PT_DIRECT and max == sigLen):
                        max = sigLen
                        selected = sigIndex
                else:
                    if self.__plugin.Settings.SigSelect == SigSelect.OPT_OPCODES:
                        if sig.iOpCount < max or (sig.eType == PatternType.PT_DIRECT and max == sig.iOpCount):
                            max = sig.iOpCount
                            selected = sigIndex
                    else:
                        wildcards = ''.join(sig.sig).count('?')
                        if wildcards < max or sig.eType == PatternType.PT_DIRECT and max == wildcards:
                            selected = sigIndex
                            max = wildcards

        if selected == -1:
            self.__plugin.log('Failed to create signature.', LogOptions.LOG_ERROR)
            return False

        sig = self.Sigs[selected]
        idaSig = ' '.join(sig.sig)
        strSig = ''

        if self.__plugin.Settings.SigType == SigType.SIG_CODE:
            patt, mask = Ida2Code(idaSig)
            strSig = patt + ' ' + mask
        elif self.__plugin.Settings.SigType == SigType.SIG_OLLY:
            strSig = Ida2Olly(idaSig)
        else:
            strSig = idaSig

        #
        # Testing sigs for now, may just leave it, it's quick
        #
        ea = BinQuery(idaSig, QueryTypes.QUERY_FIRST)

        txt = ''

        if sig.eType == PatternType.PT_DIRECT:
            txt = 'result: matches @ 0x%X, sig direct: %s' % (ea, strSig)
        elif sig.eType == PatternType.PT_FUNCTION:
            txt = 'result: matches @ 0x%X, sig function: (+0x%X) %s' % (ea, startea - sig.dwStartAddress, strSig)
        elif sig.eType == PatternType.PT_REFERENCE:
            txt = 'result: matches @ 0x%X, sig reference: %s' % (ea, strSig)

        self.__plugin.log(txt, LogOptions.LOG_RESULT)

        #
        # Qt has a clipboard widget but I didn't want to place a QT
        # requirement on using the class since it has nothing to do
        # with the Gui.  TKinter is included with Python.
        #
        r = tkinter.Tk()
        r.withdraw()
        r.clipboard_clear()
        r.clipboard_append(strSig)
        r.update()
        r.destroy()

        return True

    def AutoFunction(self) -> bool:
        """
            Generate shortest unique signature possible to current function
        """

        self._reset()

        startea = idc.get_screen_ea()
        if startea in [0, BADADDR]:
            self.__plugin.log('Current ea == BADADDR.', LogOptions.LOG_ERROR)
            return False

        if FUNC_START_EA:
            # Get function start
            func = idaapi.get_func(startea)
            if not func or func.start_ea == BADADDR:
                self.__plugin.log('Must be in a function.', LogOptions.LOG_ERROR)
                return False
            elif startea != func.start_ea:
                startea = func.start_ea
                self.__plugin.log('Using function: 0x%X' % startea, LogOptions.LOG_DEBUG)

        if not self._addRefs(startea):
            return False

        iCount = 0
        bHaveUniqueSig = False

        while not bHaveUniqueSig and len(self.Sigs):

            for sigIndex in range(0, len(self.Sigs)):

                if len(self.Sigs[sigIndex].sig) < self.__plugin.Settings.maxSigLength and self._addToSig(sigIndex):
                    if len(self.Sigs[sigIndex].sig) > 5:
                        self.Sigs[sigIndex].bUnique = BinQuery(' '.join(self.Sigs[sigIndex].sig), QueryTypes.QUERY_UNIQUE)
                else:
                    #return False
                    if sigIndex == 0:
                        self.Sigs = self.Sigs[1:]
                    elif sigIndex == len(self.Sigs) - 1:
                        self.Sigs = self.Sigs[:-1]
                    else:
                        self.Sigs = self.Sigs[:sigIndex] + self.Sigs[sigIndex+1:]

                    sigIndex = sigIndex - 1

            bHaveUniqueSig = self._haveUniqueSig()

        return self._chooseSig()

    def AutoAddress(self) -> bool:
        """
            Rather than create a sig from selection this
            gets current ea from screen and then creates
            the shortest sig possible.

            I don't really see a need for making sigs from a
            selection but can add it if enough people need it.
        """

        self._reset()

        startea = idc.get_screen_ea()
        if startea in [0, BADADDR]:
            self.__plugin.log('Click on address you want sig for.', LogOptions.LOG_ERROR)
            return False

        sig = SigCreateStruct()
        sig.dwStartAddress = startea
        sig.dwCurrentAddress = startea
        sig.eType = PatternType.PT_DIRECT

        self.Sigs.append(sig)

        while not self.Sigs[0].bUnique and len(self.Sigs[0].sig) < self.__plugin.Settings.maxSigLength:

            sigIndex = 0
            if self._addToSig(sigIndex):
                if len(self.Sigs[sigIndex].sig) > 5:
                    self.Sigs[sigIndex].bUnique = BinQuery(' '.join(self.Sigs[sigIndex].sig), QueryTypes.QUERY_UNIQUE)
            else:
                self.__plugin.log('Unable to create sig at selected address', LogOptions.LOG_ERROR)
                return False

        self._chooseSig()

#
#
# QT Gui
#
#
class PluginGui(idaapi.PluginForm):

    def __init__(self, plugin):

        global GUI_DBG_ENABLED

        idaapi.PluginForm.__init__(self)
        self.__plugin = plugin

        if GUI_DBG_ENABLED:
            self._QtDbgHelper = QTDebugHelper(self)
        else:
            self._QtDbgHelper = None

        self.closed = False

    #
    # IDA PluginForm overloaded methods
    #
    def Show(self, caption, options=None):
        if options:
            super().Show(caption, options)
            return
        # Floating window as default.
        super().Show(caption, idaapi.PluginForm.WOPN_DP_FLOATING)

    def OnCreate(self, form):
        self.widget = self.FormToPyQtWidget(form)
        self.PopulateForm()

        # Bit hackish but is needed to restore form position/size.
        # Parent widget isn't set until after this function returns.
        # The passed form is a child under the main TWidget created by IDA
        QtCore.QTimer.singleShot(1000, self._formState)

    def OnClose(self, form):
        self._formState(bSave=True)
        self.closed = True

    #
    # Connected QT events
    #
    def _sigTypeIdaClick(self):
        self.__plugin.Settings.SigType = SigType.SIG_IDA
        self.__plugin.Settings.save()

    def _sigTypeCodeClick(self):
        self.__plugin.Settings.SigType = SigType.SIG_CODE
        self.__plugin.Settings.save()

    def _sigTypeOllyClick(self):
        self.__plugin.Settings.SigType = SigType.SIG_OLLY
        self.__plugin.Settings.save()

    def _sigTest(self):

        patt = self.patt.currentText()
        mask = self.mask.text()

        sig  = ''
        st = GetSigType(patt)

        if st == SigType.SIG_CODE:
            sig = GetIdaSig(patt, mask)
        else:
            sig = GetIdaSig(patt)
            mask = ''

        if not sig:
            self.__plugin.log('Invalid sig: "%s"' % sig, LogOptions.LOG_ERROR)
            return

        self.__plugin.Settings.addHistory(patt, mask)
        self.__plugin.Settings.save()

        query = MakeBin(sig)
        result = BinSearch(query)

        #
        # Always logging tests to output so set to LOG_ERROR
        #
        if result != BADADDR:
            self.__plugin.log('Sig matched @ 0x%X' % result.ea, LogOptions.LOG_ERROR)
        else:
            self.__plugin.log('No match found', LogOptions.LOG_ERROR)

    def _sigTestSelectChanged(self, index):

        mask = ''
        try:
            mask = self.__plugin.Settings.getHistory()[index][1]
        except:
            pass

        self.mask.setText(mask)

    def _sigCurrentFunction(self):
        self.__plugin.SigMaker.AutoFunction()

    def _sigAtCursor(self):
        self.__plugin.SigMaker.AutoAddress()

    def _logLevelChanged(self, index):
        self.__plugin.Settings.LogLevel = index
        self.__plugin.Settings.save()

    def _sigSelectChanged(self, index):
        self.__plugin.Settings.SigSelect = index
        self.__plugin.Settings.save()

    def _safeDataChecked(self, checkedState):
        #
        # Checkboxes can be tristate so passed arg is not a bool
        #
        if checkedState == QtCore.Qt.Unchecked:
            self.__plugin.Settings.bOnlyReliable = False
        else:
            self.__plugin.Settings.bOnlyReliable = True

        self.__plugin.Settings.save()

    def _archiveSigmaker(self):

        global PLUGIN_DIR, HOTKEY_CONFLICT, SIGMAKER_X64_PLUGINS

        bDidMove = False

        for name in SIGMAKER_X64_PLUGINS:

            if not os.path.exists('%s/orig_sigmaker' % PLUGIN_DIR):
                self.__plugin.log('mkdir: %s/orig_sigmaker' % (PLUGIN_DIR), LogOptions.LOG_ERROR)
                os.mkdir('%s/orig_sigmaker' % PLUGIN_DIR)

            if os.path.isfile('%s/%s' % (PLUGIN_DIR, name)):
                shutil.move('%s/%s' % (PLUGIN_DIR, name), '%s/orig_sigmaker/%s' % (PLUGIN_DIR, name))
                bDidMove = True
                self.__plugin.log('Moved: %s/%s to %s/orig_sigmaker/%s' % (PLUGIN_DIR, name, PLUGIN_DIR, name), LogOptions.LOG_ERROR)

        if bDidMove:
            self.__plugin.log('SigMaker-x64 archived, restart IDA to unload it', LogOptions.LOG_ERROR)
            HOTKEY_CONFLICT = False
            SIGMAKER_X64_PLUGINS = []
            self.archiveBtn.setEnabled(False)

    def _saveHotkey(self):
        hotkey = self.hotkeyTxt.text()
        if hotkey != self.__plugin.Settings.hotkey:
            self.__plugin.Settings.hotkey = hotkey
            self.__plugin.Settings.save()
            self.__plugin.log('\npySigMaker hotkey changed to %s, IDA restart needed.' % hotkey, LogOptions.LOG_ERROR)

    def _defaultHotkey(self):
        global PLUGIN_HOTKEY
        self.hotkeyTxt.setText(PLUGIN_HOTKEY)
        if PLUGIN_HOTKEY != self.__plugin.Settings.hotkey:
            self.__plugin.Settings.hotkey = PLUGIN_HOTKEY
            self.__plugin.Settings.save()
            self.__plugin.log('\npySigMaker hotkey changed to %s (default), IDA restart needed.' % PLUGIN_HOTKEY, LogOptions.LOG_ERROR)

    #
    # Save/Restore plugin form position and size.
    #
    def _formState(self, bSave=False):

        def getWidget():
            widget, parent = None, self.widget
            while parent:
                if parent.windowTitle() == self.widget.windowTitle():
                    widget = parent
                parent = parent.parent()
            return widget

        widget = getWidget()
        if not widget:
            self.__plugin.log('Failed to save form info', LogOptions.LOG_ERROR)
            return

        if bSave:
            qrect = widget.geometry()
            x, y, w, h = qrect.x(), qrect.y(), qrect.width(), qrect.height()
            self.__plugin.Settings.saveFormInfo(x, y, w, h)
            self.__plugin.Settings.save()
            self.__plugin.log('Form saved, x={}, y={}, w={}, h={}'.format(x, y, w, h), LogOptions.LOG_DEBUG)
        else:
            x, y, w, h = self.__plugin.Settings.getFormInfo()
            if x > -1:
                widget.setGeometry(x, y, w, h)
                self.__plugin.log('Form restored: x={}, y={}, w={}, h={}'.format(x, y, w, h), LogOptions.LOG_DEBUG)
            self.patt.setCurrentText('')

    #
    # QT widget creation
    #
    def _getSigTypesBox(self):
        """ Sig type selector"""

        setting = self.__plugin.Settings.SigType

        grp = QtWidgets.QGroupBox("Sig Type")

        r1 = QtWidgets.QRadioButton(" IDA ")
        r2 = QtWidgets.QRadioButton(" Code ")
        r3 = QtWidgets.QRadioButton(" Olly ")

        r1.toggled.connect(self._sigTypeIdaClick)
        r2.toggled.connect(self._sigTypeCodeClick)
        r3.toggled.connect(self._sigTypeOllyClick)

        if setting == SigType.SIG_IDA:
            r1.setChecked(True)
        elif setting == SigType.SIG_CODE:
            r2.setChecked(True)
        elif setting == SigType.SIG_OLLY:
            r3.setChecked(True)

        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(r1)
        layout.addWidget(r2)
        layout.addWidget(r3)

        grp.setLayout(layout)

        return grp

    def _initSettings(self, layout):

        global HOTKEY_CONFLICT

        formLayout = QtWidgets.QFormLayout()
        #
        # Log to output window options
        #
        self.logOpt = QtWidgets.QComboBox()
        for s in ['Errors', 'Errors/Results', 'Debug']:
            self.logOpt.addItem(s)

        if self.__plugin.Settings.LogLevel > LogOptions.LOG_DEBUG:
            self.__plugin.Settings.LogLevel = LogOptions.LOG_DEBUG
        elif self.__plugin.Settings.LogLevel < LogOptions.LOG_ERROR:
            self.__plugin.Settings.LogLevel = LogOptions.LOG_ERROR

        self.logOpt.setCurrentIndex(self.__plugin.Settings.LogLevel)
        self.logOpt.currentIndexChanged.connect(self._logLevelChanged)

        #
        # Selecting sig from results options
        #
        self.sigSelectorOpt = QtWidgets.QComboBox()
        for s in ['Shortest Sig', 'Least Opcodes', 'Least Wildcards']:
            self.sigSelectorOpt.addItem(s)

        if self.__plugin.Settings.SigSelect > SigSelect.OPT_WILDCARDS:
            self.__plugin.Settings.SigSelect = SigSelect.OPT_WILDCARDS
        elif self.__plugin.Settings.SigSelect < SigSelect.OPT_LENGTH:
            self.__plugin.Settings.SigSelect = SigSelect.OPT_LENGTH

        self.sigSelectorOpt.setCurrentIndex(self.__plugin.Settings.SigSelect)
        self.sigSelectorOpt.currentIndexChanged.connect(self._sigSelectChanged)

        #
        # Reliable/Unreliable data option
        #
        self.safeData = QtWidgets.QCheckBox()
        self.safeData.setTristate(False)

        if self.__plugin.Settings.bOnlyReliable:
            self.safeData.setCheckState(QtCore.Qt.Checked)
        else:
            self.safeData.setCheckState(QtCore.Qt.Unchecked)

        self.safeData.stateChanged.connect(self._safeDataChecked)

        if HOTKEY_CONFLICT:
            self.archiveBtn = QtWidgets.QPushButton('Archive SigMaker-x64')
            self.archiveBtn.clicked.connect(self._archiveSigmaker)

        formLayout.addRow('Output', self.logOpt)
        formLayout.addRow('Sig Choice', self.sigSelectorOpt)
        formLayout.addRow('Reliable Data Only', self.safeData)

        layout.addLayout(formLayout)

        layout2 = QtWidgets.QHBoxLayout()

        lbl = QtWidgets.QLabel('Hotkey:')

        self.hotkeyTxt = QtWidgets.QLineEdit()
        self.hotkeyTxt.setText(self.__plugin.Settings.hotkey)
        self.hotkeySetBtn = QtWidgets.QPushButton('Set')
        self.hotkeyRestoreBtn = QtWidgets.QPushButton('Default')
        self.hotkeySetBtn.clicked.connect(self._saveHotkey)
        self.hotkeyRestoreBtn.clicked.connect(self._defaultHotkey)

        layout2.addWidget(lbl)
        layout2.addWidget(self.hotkeyTxt)
        layout2.addWidget(self.hotkeySetBtn)
        layout2.addWidget(self.hotkeyRestoreBtn)

        layout.addLayout(layout2)

        if HOTKEY_CONFLICT:
            layout.addWidget(self.archiveBtn)

    def _initMainTab(self):

        tab = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()

        btn1 = QtWidgets.QPushButton('  Sig for current function  ')
        btn2 = QtWidgets.QPushButton('  Sig at current cursor position  ')

        btn1.clicked.connect(self._sigCurrentFunction)
        btn2.clicked.connect(self._sigAtCursor)

        layout.addWidget(btn1)
        layout.addWidget(btn2)
        layout.addWidget(self._getSigTypesBox())

        tab.setLayout(layout)
        self.tabControl.addTab(tab, 'Create Sigs')

    def _initSigTest(self):

        tab = QtWidgets.QWidget()
        layout = QtWidgets.QFormLayout()

        # Need refs to patt and mask controls
        self.patt = QtWidgets.QComboBox()
        self.patt.setEditable(True)
        self.patt.setCurrentText("")

        self.mask = QtWidgets.QLineEdit()
        self.mask.setText("")

        self.patt.setInsertPolicy(QtWidgets.QComboBox.NoInsert)

        sigs = [ x[0] for x in self.__plugin.Settings.getHistory() ]
        self.patt.addItems(sigs)

        btn = QtWidgets.QPushButton(' Test ')
        btn.clicked.connect(self._sigTest)

        self.patt.currentIndexChanged.connect(self._sigTestSelectChanged)

        layout.addRow('Patt', self.patt)
        layout.addRow('Mask', self.mask)
        layout.addRow('', btn)

        tab.setLayout(layout)
        self.tabControl.addTab(tab, 'Test Sigs')

    def _initSettingsTab(self):
        tab = QtWidgets.QWidget()
        layout = QtWidgets.QVBoxLayout()
        self._initSettings(layout)
        tab.setLayout(layout)
        self.tabControl.addTab(tab, 'Settings')

    def PopulateForm(self):

        layout = QtWidgets.QVBoxLayout()
        self.tabControl = QtWidgets.QTabWidget(self.widget)
        layout.addWidget(self.tabControl)
        self.widget.setLayout(layout)

        self._initMainTab()
        self._initSigTest()
        self._initSettingsTab()

        if self._QtDbgHelper:
           self._QtDbgHelper.initDebugTab()

        return


#
# Shared settings class
#
class PluginSettings:

    def __init__(self, plugin):

        global PLUGIN_HOTKEY

        self.__plugin     = plugin
        self.__loaded     = False
        self.__configName = idaapi.get_user_idadir() + '\\pySigMaker.config'

        # 0 disables limit
        self.maxRefs = 0

        # False creates less reliable sigs IE: they break easier on updates
        self.bOnlyReliable = True

        # Controls how sig is selected from multiple unique sigs
        self.SigSelect = SigSelect.OPT_LENGTH

        # Type of sig to return
        self.SigType = SigType.SIG_IDA

        self.LogLevel = LogOptions.LOG_ERROR

        # Max sig length IE: 'E9 ?' is length of 2
        self.maxSigLength = 100

        # Sig test history
        self._history = []

        # default hot key
        self.hotkey = PLUGIN_HOTKEY

        # form info
        self.x = -1
        self.y = -1
        self.w = -1
        self.h = -1

    def saveFormInfo(self, x, y, w, h):
        self.x = x
        self.y = y
        self.w = w
        self.h = h

    def getFormInfo(self):
        return self.x, self.y, self.w, self.h

    def getHistory(self):
        return self._history

    def addHistory(self, sig, mask=''):

        # Move to front of list when used, limit history to last 10 entries
        hist = [[sig, mask]]

        for p, m in self._history:
            if p == sig:
                continue
            hist.append([p, m])
            if len(hist) == 10:
                break

        self._history = hist

    def load(self):

        if self.__loaded:
            return

        if not os.path.exists(self.__configName):
            self.__plugin.log('pySigMaker: Using defaults', LogOptions.LOG_ERROR)
            return False

        if not os.path.isfile(self.__configName):
            self.__plugin.log('pySigMaker: Using defaults', LogOptions.LOG_ERROR)
            return False


        d  = {}
        fh = None

        try:
            fh = open(self.__configName, 'rb')
            d = pickle.load(fh)
        except:
            self.__plugin.log('pySigMaker: Cfg corrupt, using defaults', LogOptions.LOG_ERROR)

        if fh:
            fh.close()

        for k, v in d.items():
            if k in self.__dict__:
                self.__dict__[k] = v
                self.__plugin.log('cfg-load: {0} {1} {2}'.format(k, v, type(v)), LogOptions.LOG_DEBUG)

        self.__loaded = True
        return d != {}

    def save(self):

        d = {}
        for k, v in self.__dict__.items():
            if k.find('__') > -1:
                continue
            d[k] = v
            self.__plugin.log('cfg-save: {0} {1} {2}'.format(k, v, type(v)), LogOptions.LOG_DEBUG)

        fh = None

        try:
            fh = open(self.__configName, 'wb')
            pickle.dump(d, fh)
        except:
            self.__plugin.log('pySigMaker: Failed to save config', LogOptions.LOG_ERROR)

        if fh:
            fh.close()

class SigMakerPlugin:

    def __init__(self):
        self.Settings = PluginSettings(self)
        self.Gui = PluginGui(self)
        self.SigMaker = SigMaker(self)
        self.Settings.load()

    def log(self, msg, log_level = LogOptions.LOG_RESULT):
        if log_level == LogOptions.LOG_ERROR or log_level <= self.Settings.LogLevel:
            print(msg)

    def showGui(self):
        if not self.Gui or self.Gui.closed:
            self.Gui = PluginGui(self)
        self.Gui.Show('pySigMaker')

def banner(hotkey):
    print('---------------------------------------------------------------------------------------------')
    print('pySigMaker: zoomgod - unknowncheats.me (credit to ajkhoury for origional SigMaker-X64 code)')
    print('            v%s - hotkey: %s' % (PLUGIN_VERSION, hotkey))
    print('---------------------------------------------------------------------------------------------')

#
# IDA Plugin Loader
#

gsigmaker = SigMakerPlugin()
banner(gsigmaker.Settings.hotkey)

def PLUGIN_ENTRY():
    return sigmaker_t()

class sigmaker_t(idaapi.plugin_t):

    flags = idaapi.PLUGIN_UNL
    comment = 'Creates code signature patterns.'
    help = ''
    wanted_name = 'pySigMaker'
    wanted_hotkey = gsigmaker.Settings.hotkey

    def init(self):
        global gsigmaker
        if not gsigmaker:
            gsigmaker = SigMakerPlugin()
        return idaapi.PLUGIN_KEEP

    def run(self, arg=None):
        global gsigmaker
        if not gsigmaker:
            gsigmaker = SigMakerPlugin()
        gsigmaker.showGui()

    def term(self):
        global gsigmaker
        gsigmaker = None


repository: WanQingGit/IDA7script
file: dsync.py
description: 常用的比较实用的ida插件脚本
url: https://github.com/WanQingGit/IDA7script/blob/c81b5fcb613201b7f03a75e5865cfbd00c845b5c/dsync.py
wanted_name: dsync
created_at: 2021-03-21 01:50:41+00:00
pushed_at: 2021-04-03 13:35:03+00:00
content:
from idaapi import *
import ida_hexrays

"""
This plugin for IDA synchronizes Hexrays decompiler views and disassembly views (from
decompiled code to disassembly by default - use the TAB key for synchronizing from
disassembly to decompiled code).

It also highlights all addresses (code and data definitions) involved. Pressing the
hotkey Ctrl-Shift-S switches synchronization on and off. Hovering over pseudocode
items will display corresponding disassembled code in a hint window. The item that
belongs to the item that is located under the cursor will be highlighted and pointed
to by an arrow.

The plugin requires IDA 7.2.
"""

__author__ = 'Dennis Elser'

HL_COLOR = 0xAD8044

# -----------------------------------------------------------------------
class idb_hook_t(IDB_Hooks):
    def __init__(self, hxehook):
        self.hxehook = hxehook
        IDB_Hooks.__init__(self)

    def savebase(self):
        self.hxehook._reset_all_colors()
        return 0

# -----------------------------------------------------------------------
class hxe_hook_t(Hexrays_Hooks):
    def __init__(self):
        Hexrays_Hooks.__init__(self)
        self.idbhook = idb_hook_t(self)
        self.idbhook.hook()
        self.pseudocode_instances = {}
        self.n_spaces = 40

    def close_pseudocode(self, vd):
        self._reset_colors(vd.view_idx, ignore_vd=True)
        refresh_idaview_anyway()
        return 0

    def create_hint(self, vd):
        result = self._get_vd_context(vd)
        if result:
            _, _, _, item_ea_list = result

            if len(item_ea_list):
                if vd.get_current_item(USE_MOUSE):
                    cur_item_ea = vd.item.it.ea
                else:
                    cur_item_ea = BADADDR

                lines = []
                for ea in item_ea_list:
                    disasm_line = generate_disasm_line(ea, 0)
                    addr = "0x%x: " % ea
                    
                    if cur_item_ea == ea:
                        prefix = COLSTR("==> %s" % addr, SCOLOR_INSN)
                    else:
                        prefix = "    " + addr

                    lines.append(prefix+disasm_line)

                lines.append("")
                lines.append(self.n_spaces * "-")
                lines.append("")
                custom_hints = "\n".join(lines)
                # ask decompiler to append default hints
                return (2, custom_hints, len(lines))
        return 0

    def curpos(self, vd):
        # workaround for a bug in IDA/Decompiler <= 7.2
        vd.refresh_cpos(USE_KEYBOARD)
        self._reset_all_colors()
        self._apply_colors(vd)
        refresh_idaview_anyway()
        return 0

    def refresh_pseudocode(self, vd):
        self._reset_all_colors(ignore_vd=True)
        return 0

    def cleanup(self):
        self._reset_all_colors()
        refresh_idaview_anyway()

        if self.idbhook:
            self.idbhook.unhook()
            self.idbhook = None
        return
 
    def _reset_colors(self, idx, ignore_vd=False):
        v = self.pseudocode_instances[idx]
        if v:
            pseudocode, lineno, color, disasm_lines = v
            if not ignore_vd and pseudocode:
                try:
                    pseudocode[lineno].bgcolor = color
                except: # wtf
                    pass
            for ea, color in disasm_lines:
                set_item_color(ea, color)
        self.pseudocode_instances.pop(idx)
        return

    def _reset_all_colors(self, ignore_vd=False):
        # restore colors
        if self.pseudocode_instances:
            for k in self.pseudocode_instances.keys():
                self._reset_colors(k, ignore_vd)
            self.pseudocode_instances = {}
        return

    def _apply_colors(self, vd):
        result = self._get_vd_context(vd)
        if result:
            pseudocode, lineno, col, item_ea_list = result
            disasm_lines = [(ea, get_item_color(ea)) for ea in item_ea_list]
            if len(item_ea_list):
                jumpto(item_ea_list[0], -1, UIJMP_IDAVIEW | UIJMP_DONTPUSH)
            self.pseudocode_instances[vd.view_idx] = (pseudocode, lineno, col, disasm_lines)

            if pseudocode:
                try:
                    pseudocode[lineno].bgcolor = HL_COLOR
                except: # wtf
                    pass
            for ea, _ in disasm_lines:
                set_item_color(ea, HL_COLOR)
        return

    def _get_item_indexes(self, line):
        indexes = []
        tag = COLOR_ON + chr(COLOR_ADDR)
        pos = line.find(tag)
        while pos != -1 and len(line[pos+len(tag):]) >= COLOR_ADDR_SIZE:
            item_idx = line[pos+len(tag):pos+len(tag)+COLOR_ADDR_SIZE]
            indexes.append(int(item_idx, 16))
            pos = line.find(tag, pos+len(tag)+COLOR_ADDR_SIZE)
        return indexes

    def _get_vd_context(self, vd):
        if vd:
            lineno = vd.cpos.lnnum
            pseudocode = vd.cfunc.get_pseudocode()

            if pseudocode and lineno != -1:
                try:
                    color = pseudocode[lineno].bgcolor
                    line = pseudocode[lineno].line
                   
                    item_idxs = self._get_item_indexes(line)
                    ea_list = {}
                    for i in item_idxs:
                        try:
                            item = vd.cfunc.treeitems.at(i)
                            if item and item.ea != BADADDR:
                                ea_list[item.ea] = None
                        except:
                            pass
                    return (pseudocode, lineno, color, sorted(ea_list.keys()))
                except:
                    pass
        return None

# -----------------------------------------------------------------------
def is_ida_version(requested):
    rv = requested.split(".")
    kv = get_kernel_version().split(".")

    count = min(len(rv), len(kv))
    if not count:
        return False

    for i in xrange(count):
        if int(kv[i]) < int(rv[i]):
            return False
    return True

# -----------------------------------------------------------------------
class Dsync(ida_idaapi.plugin_t):
    comment = ''
    help = ''
    flags = PLUGIN_MOD
    wanted_name = 'dsync'
    wanted_hotkey = 'Ctrl-Shift-S'
    hxehook = None

    def init(self):
        required_ver = "7.2"
        if not is_ida_version(required_ver) or not init_hexrays_plugin():
            msg ("[!] '%s' is inactive (IDA v%s and decompiler required).\n" % (Dsync.wanted_name, required_ver))
            return PLUGIN_SKIP

        msg("[+] '%s' loaded. %s activates/deactivates synchronization.\n" % (Dsync.wanted_name, Dsync.wanted_hotkey))
        return PLUGIN_KEEP

    def run(self, arg):
        if not Dsync.hxehook:
            Dsync.hxehook = hxe_hook_t()
            Dsync.hxehook.hook()
        else:
            Dsync.hxehook.unhook()
            Dsync.hxehook.cleanup()
            Dsync.hxehook = None

        msg("[+] %s is %sabled now.\n" % (Dsync.wanted_name, "en" if Dsync.hxehook else "dis"))

    def term(self):
        msg("[+] %s unloaded.\n" % (Dsync.wanted_name))
        if Dsync.hxehook:
            Dsync.hxehook.unhook()
            Dsync.hxehook.cleanup()
            Dsync.hxehook = None

# -----------------------------------------------------------------------
def PLUGIN_ENTRY():   
    return Dsync()


repository: mahmoudimus/.idapro
file: scripts/sigmaker.py
created_at: 2025-02-16 21:33:20+00:00
pushed_at: 2025-04-09 18:15:30+00:00
url:
https://github.com/mahmoudimus/.idapro/blob/e0bf0d58fb6de4eb6af0b87ffbd869d8c7deef08/scripts/sigmaker.py

content:
import ctypes
import ctypes.wintypes as w
import enum
import re

import ida_bytes
import ida_ida
import ida_idaapi
import ida_kernwin
import idaapi

import idc


SIGNATURE_REGEX = re.compile(r"\\x[0-9A-F]{2}")
SIGNATURE_REGEX_2 = re.compile(r"((?:0x[0-9A-F]{2})+)")


PLUGIN_NAME = "Signature Maker Python"
PLUGIN_VERSION = "1.0.3"


# Signature types and structures
class SignatureType(enum.Enum):
    IDA = 0
    x64Dbg = 1
    Signature_Mask = 2
    SignatureByteArray_Bitmask = 3


class SignatureByte:
    def __init__(self, value, isWildcard):
        self.value = value
        self.isWildcard = isWildcard


Signature = list[SignatureByte]


# Output functions
def BuildIDASignatureString(signature: Signature, doubleQM: bool = False) -> str:
    result = []
    # Build hex pattern
    for byte in signature:
        if byte.isWildcard:
            result.append("??" if doubleQM else "?")
        else:
            result.append(f"{byte.value:02X}")
        result.append(" ")
    str_result = "".join(result).rstrip()
    return str_result


def BuildByteArrayWithMaskSignatureString(signature: Signature) -> str:
    pattern = []
    mask = []
    # Build hex pattern
    for byte in signature:
        pattern.append(f"\\x{byte.value:02X}" if not byte.isWildcard else "\\x00")
        mask.append("x" if not byte.isWildcard else "?")
    return "".join(pattern) + " " + "".join(mask)


def BuildBytesWithBitmaskSignatureString(signature: Signature) -> str:
    pattern = []
    mask = []
    # Build hex pattern
    for byte in signature:
        pattern.append(f"0x{byte.value:02X}, " if not byte.isWildcard else "0x00, ")
        mask.append("1" if not byte.isWildcard else "0")
    pattern_str = "".join(pattern).rstrip(", ")
    mask_str = "".join(mask)[::-1]  # Reverse bitmask
    return pattern_str + " 0b" + mask_str


def FormatSignature(signature: Signature, sig_type: SignatureType) -> str:
    if sig_type == SignatureType.IDA:
        return BuildIDASignatureString(signature)
    elif sig_type == SignatureType.x64Dbg:
        return BuildIDASignatureString(signature, True)
    elif sig_type == SignatureType.Signature_Mask:
        return BuildByteArrayWithMaskSignatureString(signature)
    elif sig_type == SignatureType.SignatureByteArray_Bitmask:
        return BuildBytesWithBitmaskSignatureString(signature)
    return ""


# Utility functions
def AddByteToSignature(signature: Signature, address, wildcard: bool):
    byte = SignatureByte(ida_bytes.get_byte(address), wildcard)
    signature.append(byte)


def AddBytesToSignature(signature: Signature, address, count: int, wildcard: bool):
    for i in range(count):
        AddByteToSignature(signature, address + i, wildcard)


def TrimSignature(signature: Signature):
    while signature and signature[-1].isWildcard:
        signature.pop()


def SetClipboardText(text: str) -> bool:

    GMEM_MOVEABLE = 0x0002
    GMEM_ZEROINIT = 0x0040
    CF_TEXT = 1

    u32 = ctypes.WinDLL("user32")
    k32 = ctypes.WinDLL("kernel32")

    GlobalAlloc = k32.GlobalAlloc
    GlobalAlloc.argtypes = w.UINT, w.UINT
    GlobalAlloc.restype = w.HGLOBAL

    GlobalFree = k32.GlobalFree
    GlobalFree.argtypes = (w.HGLOBAL,)
    GlobalFree.restype = w.HGLOBAL

    SetClipboardData = u32.SetClipboardData
    SetClipboardData.argtypes = w.UINT, w.HANDLE
    SetClipboardData.restype = w.HANDLE

    EmptyClipboard = u32.EmptyClipboard

    OpenClipboard = u32.OpenClipboard
    OpenClipboard.argtypes = (w.HWND,)
    OpenClipboard.restype = w.BOOL

    GlobalLock = k32.GlobalLock
    GlobalLock.argtypes = (w.HGLOBAL,)
    GlobalLock.restype = w.LPVOID

    GlobalUnlock = k32.GlobalUnlock
    GlobalUnlock.argtypes = (w.HGLOBAL,)
    GlobalUnlock.restype = w.BOOL

    GetClipboardData = u32.GetClipboardData
    GetClipboardData.argtypes = (w.UINT,)
    GetClipboardData.restype = w.HANDLE

    CloseClipboard = u32.CloseClipboard
    CloseClipboard.argtypes = None
    CloseClipboard.restype = w.BOOL

    if not text:
        return False

    if not OpenClipboard(None) or not EmptyClipboard():
        return False

    try:
        # Allocate global memory
        h_mem = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len(text) + 1)
        if not h_mem:
            return False

        # Lock the handle and copy the text
        lp_str = GlobalLock(h_mem)
        if not lp_str:
            GlobalFree(h_mem)
            return False

        ctypes.memmove(lp_str, text.encode("utf-8"), len(text))
        GlobalUnlock(h_mem)

        # Set the clipboard data
        if not SetClipboardData(CF_TEXT, h_mem):
            GlobalFree(h_mem)
            return False
    except Exception as e:
        print(f"Failed to set clipboard text: {e}")
        return False
    finally:
        CloseClipboard()

    return True


def GetRegexMatches(string: str, regex: re.Pattern, matches: list[str]) -> bool:
    matches.clear()
    matches.extend(re.findall(regex, string))
    return bool(matches)


class Unexpected(Exception):
    pass


class SignatureMakerForm(ida_kernwin.Form):
    FormChangeCb: ida_kernwin.Form.FormChangeCb
    rAction: ida_kernwin.Form.RadGroupControl
    rOutputFormat: ida_kernwin.Form.RadGroupControl
    cGroupOptions: ida_kernwin.Form.ChkGroupControl

    def __init__(self):
        form = f"""\
BUTTON YES* OK
BUTTON CANCEL Cancel
{PLUGIN_NAME} v{PLUGIN_VERSION}
{{FormChangeCb}}
Select action:
<Create unique Signature for current code address:{{rCreateUniqueSig}}>
<Find shortest XREF Signature for current data or code address:{{rFindXRefSig}}>
<Copy selected code:{{rCopyCode}}>
<Search for a signature:{{rSearchSignature}}>{{rAction}}>

Output format:
<IDA Signature:{{rIDASig}}>
<x64Dbg Signature:{{rx64DbgSig}}>
<C Byte Array Signature + String mask:{{rByteArrayMaskSig}}>
<C Raw Bytes Signature + Bitmask:{{rRawBytesBitmaskSig}}>{{rOutputFormat}}>

Options:
<Wildcards for operands:{{cWildcardOperands}}>
<Continue when leaving function scope:{{cContinueOutside}}>{{cGroupOptions}}>
"""
        controls = {
            "FormChangeCb": ida_kernwin.Form.FormChangeCb(self.OnFormChange),
            "rAction": ida_kernwin.Form.RadGroupControl(
                (
                    "rCreateUniqueSig",
                    "rFindXRefSig",
                    "rCopyCode",
                    "rSearchSignature",
                )
            ),
            "rOutputFormat": ida_kernwin.Form.RadGroupControl(
                (
                    "rIDASig",
                    "rx64DbgSig",
                    "rByteArrayMaskSig",
                    "rRawBytesBitmaskSig",
                )
            ),
            "cGroupOptions": ida_kernwin.Form.ChkGroupControl(
                ("cWildcardOperands", "cContinueOutside")
            ),
        }
        super().__init__(form, controls)

    def OnFormChange(self, fid):
        # Debug output for when the form changes
        # print(f"Form changed, fid: {fid}", self.rAction.id, self.rOutputFormat.id, self.cGroupOptions.id)
        if fid == self.rAction.id:
            print(
                f"Action [{fid}] rAction changed: {self.GetControlValue(self.rAction):06x}"
            )
        elif fid == self.rOutputFormat.id:
            print(
                f"Action [{fid}] rOutputFormat changed: {self.GetControlValue(self.rOutputFormat):06x}"
            )
        elif fid == self.cGroupOptions.id:
            print(
                f"Action [{fid}] cGroupOptions changed: {self.GetControlValue(self.cGroupOptions):06x}"
            )
        else:
            print(">>fid:%d" % fid)
        return 1


# Plugin specific definitions
class PySigMaker(ida_idaapi.plugin_t):
    IS_ARM = False

    def run(self, arg):
        self.run_plugin()

    def run_plugin(self):
        # Check what processor we have
        PySigMaker.IS_ARM = self.IsARM()

        form = SignatureMakerForm()
        form.Compile()

        # Execute the form and get results
        ok = form.Execute()
        if ok:
            action = form.rAction.value
            output_format = form.rOutputFormat.value
            wildcard_operands = form.cGroupOptions.value & 1
            continue_outside_of_function = form.cGroupOptions.value & 2

        # Don't forget to free the form after execution
        form.Free()

        sig_type = SignatureType(output_format)

        if action == 0:
            # Find unique signature for current address
            ea = idc.get_screen_ea()
            idaapi.show_wait_box("Generating signature...")
            signatures = self.GenerateUniqueSignatureForEA(
                ea, wildcard_operands, continue_outside_of_function
            )
            self.PrintSignatureForEA(signatures, ea, sig_type)
            idaapi.hide_wait_box()

        elif action == 1:
            # Find XREFs for current selection, generate signatures up to 250 bytes length
            ea = idc.get_screen_ea()
            xref_signatures = []
            idaapi.show_wait_box(
                "Finding references and generating signatures. This can take a while..."
            )
            self.FindXRefs(
                ea,
                wildcard_operands,
                continue_outside_of_function,
                xref_signatures,
                250,
            )
            # Print top 5 shortest signatures
            self.PrintXRefSignaturesForEA(ea, xref_signatures, sig_type, 5)
            idaapi.hide_wait_box()

        elif action == 2:
            # Print selected code as signature
            start, end = ida_kernwin.read_range_selection(idaapi.get_current_viewer())
            if start and end:
                idaapi.show_wait_box("Please stand by...")
                self.PrintSelectedCode(start, end, sig_type, wildcard_operands)
                idaapi.hide_wait_box()
            else:
                idc.msg("Select a range to copy the code\n")

        elif action == 3:
            # Search for a signature
            input_signature = idaapi.ask_str("", idaapi.HIST_SRCH, "Enter a signature")
            if input_signature:
                idaapi.show_wait_box("Searching...")
                self.SearchSignatureString(input_signature)
                idaapi.hide_wait_box()

    def IsARM(self) -> bool:
        return "ARM" in ida_idaapi.get_inf_structure().procname

    def GetOperandOffsetARM(self, instruction, operand_offset, operand_length):
        for op in instruction.ops:
            if op.type in {
                idaapi.o_mem,
                idaapi.o_far,
                idaapi.o_near,
                idaapi.o_phrase,
                idaapi.o_displ,
                idaapi.o_imm,
            }:
                operand_offset[0] = op.offb
                operand_length[0] = (
                    3 if instruction.size == 4 else 7 if instruction.size == 8 else 0
                )
                return True
        return False

    def GetOperand(self, instruction, operand_offset, operand_length):
        if self.IS_ARM:
            return self.GetOperandOffsetARM(instruction, operand_offset, operand_length)

        for op in instruction.ops:
            if op.type != idaapi.o_void and op.offb != 0:
                operand_offset[0] = op.offb
                operand_length[0] = instruction.size - op.offb
                return True
        return False

    def FindSignatureOccurences(self, ida_signature: str) -> list:
        binary_pattern = idaapi.compiled_binpat_vec_t()
        idaapi.parse_binpat_str(
            binary_pattern, ida_ida.cvar.inf.min_ea, ida_signature, 16
        )

        results = []
        ea = ida_ida.cvar.inf.min_ea
        while True:
            occurence = ida_bytes.bin_search(
                ea,
                ida_ida.cvar.inf.max_ea,
                binary_pattern,
                ida_bytes.BIN_SEARCH_NOCASE | ida_bytes.BIN_SEARCH_FORWARD,
            )
            if occurence == idaapi.BADADDR:
                return results
            results.append(occurence)
            ea = occurence + 1

    def IsSignatureUnique(self, ida_signature: str) -> bool:
        return len(self.FindSignatureOccurences(ida_signature)) == 1

    def GenerateUniqueSignatureForEA(
        self,
        ea,
        wildcard_operands,
        continue_outside_of_function,
        max_signature_length=1000,
        ask_longer_signature=True,
    ):
        if ea == idaapi.BADADDR:
            raise Unexpected("Invalid address")
        if not idaapi.is_code(ida_bytes.get_flags(ea)):
            raise Unexpected("Cannot create code signature for data")

        signature = []
        sig_part_length = 0
        current_function = idaapi.get_func(ea)
        current_address = ea

        while True:
            if idaapi.user_cancelled():
                raise Unexpected("Aborted")

            instruction = idaapi.insn_t()
            current_instruction_length = idaapi.decode_insn(
                instruction, current_address
            )
            if current_instruction_length <= 0:
                if not signature:
                    raise Unexpected("Failed to decode first instruction")

                idc.msg(
                    f"Signature reached end of executable code @ {current_address:X}\n"
                )
                signature_string = BuildIDASignatureString(signature)
                idc.msg(f"NOT UNIQUE Signature for {ea:X}: {signature_string}\n")
                raise Unexpected("Signature not unique")

            if sig_part_length > max_signature_length:
                if ask_longer_signature:
                    result = idaapi.ask_yn(
                        idaapi.ASKBTN_YES,
                        f"Signature is already at {len(signature)} bytes. Continue?",
                    )
                    if result == 1:  # Yes
                        sig_part_length = 0
                    elif result == 0:  # No
                        signature_string = BuildIDASignatureString(signature)
                        idc.msg(
                            f"NOT UNIQUE Signature for {ea:X}: {signature_string}\n"
                        )
                        raise Unexpected("Signature not unique")
                    else:  # Cancel
                        raise Unexpected("Aborted")
                else:
                    raise Unexpected("Signature exceeded maximum length")

            sig_part_length += current_instruction_length

            operand_offset = [0]
            operand_length = [0]
            if (
                wildcard_operands
                and self.GetOperand(instruction, operand_offset, operand_length)
                and operand_length[0] > 0
            ):
                AddBytesToSignature(
                    signature, current_address, operand_offset[0], False
                )
                AddBytesToSignature(
                    signature,
                    current_address + operand_offset[0],
                    operand_length[0],
                    True,
                )
                if operand_offset[0] == 0:
                    AddBytesToSignature(
                        signature,
                        current_address + operand_length[0],
                        current_instruction_length - operand_length[0],
                        False,
                    )
            else:
                AddBytesToSignature(
                    signature, current_address, current_instruction_length, False
                )

            current_sig = BuildIDASignatureString(signature)
            if self.IsSignatureUnique(current_sig):
                TrimSignature(signature)
                return signature

            current_address += current_instruction_length

            if (
                not continue_outside_of_function
                and current_function
                and idaapi.get_func(current_address) != current_function
            ):
                raise Unexpected("Signature left function scope")

        raise Unexpected("Unknown")

    def GenerateSignatureForEARange(self, ea_start, ea_end, wildcard_operands):
        if ea_start == idaapi.BADADDR or ea_end == idaapi.BADADDR:
            raise Unexpected("Invalid address")

        signature = []
        sig_part_length = 0

        if not idaapi.is_code(ida_bytes.get_flags(ea_start)):
            AddBytesToSignature(signature, ea_start, ea_end - ea_start, False)
            return signature

        current_address = ea_start
        while True:
            if idaapi.user_cancelled():
                raise Unexpected("Aborted")

            instruction = idaapi.insn_t()
            current_instruction_length = idaapi.decode_insn(
                instruction, current_address
            )
            if current_instruction_length <= 0:
                if not signature:
                    raise Unexpected("Failed to decode first instruction")

                idc.msg(
                    f"Signature reached end of executable code @ {current_address:X}\n"
                )
                if current_address < ea_end:
                    AddBytesToSignature(
                        signature, current_address, ea_end - current_address, False
                    )
                TrimSignature(signature)
                return signature

            sig_part_length += current_instruction_length

            operand_offset = [0]
            operand_length = [0]
            if (
                wildcard_operands
                and self.GetOperand(instruction, operand_offset, operand_length)
                and operand_length[0] > 0
            ):
                AddBytesToSignature(
                    signature, current_address, operand_offset[0], False
                )
                AddBytesToSignature(
                    signature,
                    current_address + operand_offset[0],
                    operand_length[0],
                    True,
                )
                if operand_offset[0] == 0:
                    AddBytesToSignature(
                        signature,
                        current_address + operand_length[0],
                        current_instruction_length - operand_length[0],
                        False,
                    )
            else:
                AddBytesToSignature(
                    signature, current_address, current_instruction_length, False
                )

            current_address += current_instruction_length

            if current_address >= ea_end:
                TrimSignature(signature)
                return signature

        raise Unexpected("Unknown")

    def PrintSignatureForEA(self, signature, ea, sig_type):
        if not signature:
            idc.msg(f"Error: {signature}\n")
            return
        signature_str = FormatSignature(signature, sig_type)
        idc.msg(f"Signature for {ea:X}: {signature_str}\n")
        if not SetClipboardText(signature_str):
            idc.msg("Failed to copy to clipboard!")

    def FindXRefs(
        self,
        ea,
        wildcard_operands,
        continue_outside_of_function,
        xref_signatures,
        max_signature_length,
    ):
        xref = idaapi.xrefblk_t()

        xref_count = 0
        for xref_ok in iter(lambda: xref.first_to(ea, idaapi.XREF_FAR), False):
            if not idaapi.is_code(ida_bytes.get_flags(xref.frm)):
                continue
            xref_count += 1

        shortest_signature_length = max_signature_length + 1

        for i, xref_ok in enumerate(
            iter(lambda: xref.first_to(ea, idaapi.XREF_FAR), False)
        ):
            if idaapi.user_cancelled():
                break

            if not idaapi.is_code(ida_bytes.get_flags(xref.frm)):
                continue

            idaapi.replace_wait_box(
                f"Processing xref {i + 1} of {xref_count} ({(i / xref_count) * 100.0:.1f}%)...\n\nSuitable Signatures: {len(xref_signatures)}\nShortest Signature: {shortest_signature_length if shortest_signature_length <= max_signature_length else 0} Bytes"
            )

            signature = self.GenerateUniqueSignatureForEA(
                xref.frm,
                wildcard_operands,
                continue_outside_of_function,
                max_signature_length,
                False,
            )
            if not signature:
                continue

            if len(signature) < shortest_signature_length:
                shortest_signature_length = len(signature)

            xref_signatures.append((xref.frm, signature))

            xref_signatures.sort(key=lambda x: len(x[1]))

    def PrintXRefSignaturesForEA(self, ea, xref_signatures, sig_type, top_count):
        if not xref_signatures:
            idc.msg("No XREFs have been found for your address\n")
            return

        top_length = min(top_count, len(xref_signatures))
        idc.msg(
            f"Top {top_length} Signatures out of {len(xref_signatures)} xrefs for {ea:X}:\n"
        )
        for i in range(top_length):
            origin_address, signature = xref_signatures[i]
            signature_str = FormatSignature(signature, sig_type)
            idc.msg(f"XREF Signature #{i + 1} @ {origin_address:X}: {signature_str}\n")

            if i == 0:
                SetClipboardText(signature_str)

    def PrintSelectedCode(self, start, end, sig_type, wildcard_operands):
        selection_size = end - start
        assert selection_size > 0

        signature = self.GenerateSignatureForEARange(start, end, wildcard_operands)
        if not signature:
            idc.msg(f"Error: {signature}\n")
            return

        signature_str = FormatSignature(signature, sig_type)
        idc.msg(f"Code for {start:X}-{end:X}: {signature_str}\n")
        SetClipboardText(signature_str)

    def SearchSignatureString(self, input):
        converted_signature_string = ""
        string_mask = ""

        match = re.search(r"x[x?]+", input)
        if match:
            string_mask = match.group(0)
        else:
            match = re.search(r"0b[0,1]+", input)
            if match:
                bits = match.group(0)[2:]
                reversed_bits = bits[::-1]
                string_mask = "".join("x" if b == "1" else "?" for b in reversed_bits)

        if string_mask:
            raw_byte_strings = []
            if GetRegexMatches(input, SIGNATURE_REGEX, raw_byte_strings) and len(
                raw_byte_strings
            ) == len(string_mask):
                converted_signature = []
                for i, m in enumerate(raw_byte_strings):
                    b = SignatureByte(int(m[2:], 16), string_mask[i] == "?")
                    converted_signature.append(b)
                converted_signature_string = BuildIDASignatureString(
                    converted_signature
                )
            elif GetRegexMatches(input, SIGNATURE_REGEX_2, raw_byte_strings) and len(
                raw_byte_strings
            ) == len(string_mask):
                converted_signature = []
                for i, m in enumerate(raw_byte_strings):
                    b = SignatureByte(int(m[2:], 16), string_mask[i] == "?")
                    converted_signature.append(b)
                converted_signature_string = BuildIDASignatureString(
                    converted_signature
                )
            else:
                idc.msg(
                    f'Detected mask "{string_mask}" but failed to match corresponding bytes\n'
                )
        else:
            input = re.sub(r"[)(\[\]]+", "", input)
            input = re.sub(r"^\s+", "", input)
            input = re.sub(r"[? ]+$", "", input) + " "
            input = re.sub(r"\\?\\x", "", input)  # Simplify hex pattern matching
            input = re.sub(r"\s+", " ", input)  # Normalize spaces
            converted_signature_string = input

        if not converted_signature_string:
            idc.msg("Unrecognized signature type\n")
            return

        idc.msg(f"Signature: {converted_signature_string}\n")
        signature_matches = self.FindSignatureOccurences(converted_signature_string)
        if not signature_matches:
            idc.msg("Signature does not match!\n")
            return
        for ea in signature_matches:
            idc.msg(f"Match @ {ea:X}\n")


# Register the plugin
def PLUGIN_ENTRY():
    return PySigMaker()


# PySigMaker().run(None)
# form = SignatureMakerForm()
# form.Compile()

# # Execute the form and get results
# ok = form.Execute()

# form.Free()


repository: sfinktah/sfida
file: idarest_master.py
description: idapro python toolkit
url: https://github.com/sfinktah/sfida/blob/b9a8307b5c3e3cbfeb2372757a0b3d0da84142ea/idarest_master.py
wanted_name: idarest75 master
comment: IDA Rest API Master Controller
created_at: 2021-03-01 10:05:12+00:00
pushed_at: 2024-10-23 03:13:14+00:00
content:
import socket, atexit, sys
from underscore3 import _
try:
    from .idarest_mixins import IdaRestConfiguration, IdaRestLog
except:
    from idarest_mixins import IdaRestConfiguration, IdaRestLog

#  idarest_master_plugin_t.config['master_debug'] = False
#  idarest_master_plugin_t.config['master_info'] = False
#  idarest_master_plugin_t.config['api_prefix'] = '/ida/api/v1.0'
#  idarest_master_plugin_t.config['master_host'] = "127.0.0.1"
#  idarest_master_plugin_t.config['master_port'] = 28612 # hash('idarest75') & 0xffff
MENU_PATH = 'Edit/Other'

interactive = False

try:
    import idc
    import ida_idaapi
    import ida_kernwin
    import idaapi
    import idautils
    from PyQt5 import QtWidgets
except:
    class idc:
        @staticmethod
        def msg(s):
            if idarest_master_plugin_t.config['master_debug']: print(s)

    class ida_idaapi:
        plugin_t = object
        PLUGIN_SKIP = PLUGIN_UNL = PLUGIN_KEEP = 0

class idarest_master_plugin_t(IdaRestConfiguration, IdaRestLog, ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_UNL
    comment = "IDA Rest API Master Controller"
    help = "Keeps track of idarest75 clients"
    wanted_name = "idarest75 master"
    wanted_hotkey = ""

    def init(self):
        super(idarest_master_plugin_t, self).__init__()
        self.load_configuration()
        if idarest_master_plugin_t.config['master_info']: print("[idarest_master_plugin_t::init]")
        self.master = None

        if not idarest_master_plugin_t.test_bind_port(idarest_master_plugin_t.config['master_port']):
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master_plugin_t::init] skipping (port is already bound)")
            return idaapi.PLUGIN_SKIP

        self.master = idarest_master()
        idarest_master_plugin_t.instance = self

        #  def cleanup():
            # TODO: make master able to clean up! ffs
            #  self.log("**master.atexit** cleanup")
            #  if worker and worker.is_alive():
                #  self.log("[idarest_master_plugin_t::start::cleanup] stopping..\n")
                #  worker.stop()
                #  self.log("[idarest_master_plugin_t::start::cleanup] joining..\n")
                #  worker.join()
                #  self.log("[idarest_master_plugin_t::start::cleanup] stopped\n")
#  
            #  if timer and timer.is_alive() and not timer.stopped():
                #  self.log("[idarest_master_plugin_t::start::cleanup] stopping..\n")
                #  timer.stop()
                #  self.log("[idarest_master_plugin_t::start::cleanup] joining..\n")
                #  timer.join()
                #  self.log("[idarest_master_plugin_t::start::cleanup] stopped\n")

        #  print('[idarest_master_plugin_t::start] registered atexit cleanup')

        #  atexit.register(cleanup)
        return idaapi.PLUGIN_KEEP

    def run(*args):
        pass

    def term(self):
        if self.master:
            self.master.stop()
        pass

    @staticmethod
    def test_bind_port(port):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            try:
                # s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                s.bind((idarest_master_plugin_t.config['master_bind_ip'], port))
            except socket.error as e:
                return False
        return True

def idarest_master():
    from http.server import BaseHTTPRequestHandler, HTTPServer
    from socketserver import ThreadingMixIn
    import threading
    import urllib.request, urllib.error, urllib.parse as urlparse
    import requests
    import json
    import time
    import re

    def asBytes(s):
        if isinstance(s, str):
            return s.encode('utf-8')
        return s

    class HTTPRequestError(BaseException):
        def __init__(self, msg, code):
            self.msg = msg
            self.code = code

    class Handler(BaseHTTPRequestHandler):
        hosts = dict()

        def log_message(self, format, *args):
            return

        def register(self, args):
            host, port = args['host'], args['port']
            key = host + ':' + port
            if key in self.hosts:
                if interactive or idarest_master_plugin_t.config['master_debug']: print("[idarest_master::Handler::register] replacing existing host {}".format(key))
            self.hosts[key] = value = dict({
                    'host': args['host'],
                    'port': args['port'],
                    'idb': args['idb'],
                    'alive': time.time(),
                    'failed': 0,
            })
            return value

        def unregister(self, args):
            host, port = args['host'], args['port']
            key = host + ':' + port
            if key in self.hosts:
                if interactive or idarest_master_plugin_t.config['master_debug']: print("[idarest_master::Handler::unregister] removing existing host {}".format(key))
                value = self.hosts.pop(key)
            else:
                value = dict({
                    'host': args['host'],
                    'port': args['port'],
                    'error': 'not registered',
                })
                    

            return value

        @staticmethod
        def get_json(hosts, args, readonly=False):
            #  r = requests.post(self.url, data=self.args)
            results = dict()
            start = time.time()
            if readonly:
                for k, host in hosts.items():
                    if idarest_master_plugin_t.config['master_debug']: print("alive: {}".format(start - host['alive']))
                    if start - host['alive'] < 90:
                        results[host['idb']] = 'http://{}:{}{}/'.format(host['host'], host['port'], idarest_master_plugin_t.config['api_prefix'])
                    #  else:
                        #  results[host['idb']] = start - host['alive']
                return results

            for k, host in hosts.copy().items():
                start = time.time()
                url = 'http://{}:{}{}/echo'.format(host['host'], host['port'], idarest_master_plugin_t.config['api_prefix'])
                try:
                    connect_timeout = 10
                    read_timeout = 10
                    r = requests.get(url, params=args, timeout=(connect_timeout, read_timeout))
                    if r.status_code == 200:
                        hosts[k]['alive'] = start
                        hosts[k]['rtime'] = r.elapsed.total_seconds()
                        #  hosts[k]['info'] = r.json()
                        results[k] = host
                except Exception as e:
                    results[k] = str(type(e))
                    hosts[k]['failed'] += 1
                    if hosts[k]['failed'] > 4:
                        hosts.pop(k)

            return results


        def show(self, args):
            return self.get_json(self.hosts, {'ping': time.time()}, readonly=True)

        def fail(self, args):
            if 'idb' not in args:
                raise HTTPRequestError("idb param not specified", 400)
            found = _.find(self.hosts, lambda x, *a: x['idb'] == args['idb'])
            print("[fail] found:{}, type(found):{}".format(found, type(found)))
            keys = [x for x, y in self.hosts.items() if y == found]
            if keys:
                for key in keys:
                    if idarest_master_plugin_t.config['master_debug']: print("[idarest_master::Handler::unregister] removing existing host {}".format(key))
                    value = self.hosts.pop(key)
            else:
                value = dict({
                    'host': args['host'],
                    'port': args['port'],
                    'error': 'not registered',
                })
                    

            return value


            return self.get_json(self.hosts, {'ping': time.time()}, readonly=True)

        def _extract_query_map(self):
            query = urlparse.urlparse(self.path).query
            qd = urlparse.parse_qs(query)
            args = {}
            for k, v in qd.items():
                if len(v) != 1:
                    raise HTTPRequestError(
                        "Query param specified multiple times : " + k,
                        400)
                args[k.lower()] = v[0]
                if idarest_master_plugin_t.config['master_debug']: print('args["{}"]: "{}"'.format(k.lower(), v[0]))
            return args

        def send_origin_headers(self):
            if self.headers.get('Origin', '') == 'null':
                self.send_header('Access-Control-Allow-Origin', self.headers.get('Origin'))
            self.send_header('Vary', 'Origin')

        def do_GET(self):
            try:
                args = self._extract_query_map()
            except HTTPRequestError as e:
                self.send_error(e.code, e.msg)
                return

            path = re.sub(r'.*/', '', urlparse.urlparse(self.path).path)
            if path == 'register':
                message = self.register(args)
            elif path == 'unregister':
                message = self.unregister(args)
            elif path == 'show':
                message = self.show(args)
            elif path == 'fail':
                message = self.fail(args)
            elif path == 'term':
                globals()['instance'].term()
            elif path == 'restart':
                # TODO: actually restart
                globals()['instance'].term()
            else:
                self.send_error(400, "unknown route: " + path)
                return

            self.send_response(200)
            self.send_origin_headers()
            self.end_headers()
            self.wfile.write(asBytes(json.dumps(message)))
            return

    class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
        allow_reuse_address = True

    # https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread
    class Timer(threading.Thread):
        def __init__(self,  *args, **kwargs):
            super(Timer, self).__init__(*args, **kwargs)
            self._stop_event = threading.Event()

        def run(self):
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Timer::run] started")
            while True:
                if self._stop_event.wait(60.0):
                    break
                result = Handler.get_json(Handler.hosts, {'ping': time.time()})
                if idarest_master_plugin_t.config['master_debug']: print("[idarest_master::Timer::run] {}".format(result))
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Timer::run] stopped")

            #  if not self.running:
                #  self.running = True
                #  while self.running:
                    #  time.sleep(60.0 - ((time.time() - self.starttime) % 60.0))
                    #  if idarest_master_plugin_t.config['master_debug']: print(Handler.get_json(Handler.hosts, {'ping': time.time()}))
                #  if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Timer::run] stopped")

        def stop(self):
            if self.is_alive():
                if self.stopped():
                    if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Timer::stop] already stopping...")
                else:
                    if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Timer::stop] stopping...")
                    self._stop_event.set()
            else:
                if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Timer::stop] not running")

        def stopped(self):
            return self._stop_event.is_set()

    class Worker(threading.Thread):
        def __init__(self, host, port):
            threading.Thread.__init__(self)
            self.httpd = ThreadedHTTPServer((host, port), Handler)
            self.host = host
            self.port = port

        def run(self):
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Worker::run] master httpd starting...")
            self.httpd.serve_forever()
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Worker::run] master httpd started (well stopped now, i guess)")

        def stop(self):
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Worker::stop] master httpd shutdown...")
            self.httpd.shutdown()
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Worker::stop] master httpd server_close...")
            self.httpd.server_close()
            if idarest_master_plugin_t.config['master_info']: print("[idarest_master::Worker::stop] master httpd stopped")

    class Master:
        def __init__(self):
            # self.worker = Worker('127.0.0.1', 28612)
            self.worker = Worker('0.0.0.0', 28612)
            self.worker.start()
            self.test_worker = Timer()
            self.test_worker.start()

        def stop(self):
            self.worker.stop()
            self.test_worker.stop()

    def main():
        if interactive or idarest_master_plugin_t.config['master_info']: print("[idarest_master::main] starting master")
        master = Master()
        #  main.master = master
        return master

    return main()

def PLUGIN_ENTRY():
    globals()['instance'] = idarest_master_plugin_t()
    return globals()['instance']


if sys.stdin and sys.stdin.isatty():
    # running interactively
    print("running interactively")
    interactive = True

if __name__ == "__main__":
    master = idarest_master()


repository: TrungNguyen1909/aarch64-sysreg-ida
file: aarch64_sysreg.py
description: A IDA plugin to show ARM MSRs nicely
wanted_name: Aarch64 SysReg
comment: Aarch64 Apple SysReg extension
created_at: 2022-02-11 06:40:47+00:00
pushed_at: 2023-01-04 00:15:57+00:00
url:
https://github.com/TrungNguyen1909/aarch64-sysreg-ida/blob/3f9d3202abbffdd04bea1b58f1530674719634a6/aarch64_sysreg.py

content:
'''
Copyright (c) 2022 ntrung03

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.

Based on https://gist.github.com/bazad/42054285391c6e0dcd0ede4b5f969ad2 by Brandon Azad
Based on https://gist.github.com/dougallj/7a75a3be1ec69ca550e7c36dc75e0d6f by Dougall J
'''

import idaapi
import ida_hexrays
import os
import json

ITYPE_MRS = idaapi.CUSTOM_INSN_ITYPE + 13
ITYPE_MSR = ITYPE_MRS + 1
ITYPE_SYS = ITYPE_MSR + 1

'''
Download apple_regs.json from https://github.com/AsahiLinux/m1n1/blob/main/tools/apple_regs.json
and put it in the same directory
'''
APPLE_REGS_CFGFILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "apple_regs.json")

# AArch64 PSTATE accesses (op0 = 0b00, CRn = 0b0100).
PSTATE_ACCESS = {
    0b011 : 'UAO',
    0b100 : 'PAN',
    0b101 : 'SPSel',
    0b110 : 'DAIFSet',
    0b111 : 'DAIFClr',
}

# AArch64 system registers  (op0 = 0b01, 0b10, 0b11).
SYSTEM_REGISTERS = {
    # SysReg_xml_v86A-2020-03
    'S1_0_c7_c1_0'   : ( 'IC IALLUIS', 'Instruction Cache Invalidate All to PoU, Inner Shareable' ),
    'S1_0_c7_c5_0'   : ( 'IC IALLU', 'Instruction Cache Invalidate All to PoU' ),
    'S1_0_c7_c6_1'   : ( 'DC IVAC', 'Data or unified Cache line Invalidate by VA to PoC' ),
    'S1_0_c7_c6_2'   : ( 'DC ISW', 'Data or unified Cache line Invalidate by Set/Way' ),
    'S1_0_c7_c6_3'   : ( 'DC IGVAC', 'Data, Allocation Tag or unified Cache line Invalidate of Allocation Tags by VA to PoC' ),
    'S1_0_c7_c6_4'   : ( 'DC IGSW', 'Data, Allocation Tag or unified Cache line Invalidate of Allocation Tags by Set/Way' ),
    'S1_0_c7_c6_5'   : ( 'DC IGDVAC', 'Data, Allocation Tag or unified Cache line Invalidate of Allocation Tags by VA to PoC' ),
    'S1_0_c7_c6_6'   : ( 'DC IGDSW', 'Data, Allocation Tag or unified Cache line Invalidate of Data and Allocation Tags by Set/Way' ),
    'S1_0_c7_c8_0'   : ( 'AT S1E1R', 'Address Translate Stage 1 EL1 Read' ),
    'S1_0_c7_c8_1'   : ( 'AT S1E1W', 'Address Translate Stage 1 EL1 Write' ),
    'S1_0_c7_c8_2'   : ( 'AT S1E0R', 'Address Translate Stage 1 EL0 Read' ),
    'S1_0_c7_c8_3'   : ( 'AT S1E0W', 'Address Translate Stage 1 EL0 Write' ),
    'S1_0_c7_c9_0'   : ( 'AT S1E1RP', 'Address Translate Stage 1 EL1 Read PAN' ),
    'S1_0_c7_c9_1'   : ( 'AT S1E1WP', 'Address Translate Stage 1 EL1 Write PAN' ),
    'S1_0_c7_c10_2'  : ( 'DC CSW', 'Data or unified Cache line Clean by Set/Way' ),
    'S1_0_c7_c10_4'  : ( 'DC CGSW', 'Data, Allocation Tag or unified Cache line Clean of Allocation Tags by Set/Way' ),
    'S1_0_c7_c10_6'  : ( 'DC CGDSW', 'Data, Allocation Tag or unified Cache line Clean of Data and Allocation Tags by Set/Way' ),
    'S1_0_c7_c14_2'  : ( 'DC CISW', 'Data or unified Cache line Clean and Invalidate by Set/Way' ),
    'S1_0_c7_c14_4'  : ( 'DC CIGSW', 'Data, Allocation Tag or unified Cache line Clean and Invalidate of Allocation Tags by Set/Way' ),
    'S1_0_c7_c14_6'  : ( 'DC CIGDSW', 'Data, Allocation Tag or unified Cache line Clean and Invalidate of Data and Allocation Tags by Set/Way' ),
    'S1_0_c8_c1_0'   : ( 'TLBI VMALLE1OS', 'TLB Invalidate by VMID, All at stage 1, EL1, Outer Shareable' ),
    'S1_0_c8_c1_1'   : ( 'TLBI VAE1OS', 'TLB Invalidate by VA, EL1, Outer Shareable' ),
    'S1_0_c8_c1_2'   : ( 'TLBI ASIDE1OS', 'TLB Invalidate by ASID, EL1, Outer Shareable' ),
    'S1_0_c8_c1_3'   : ( 'TLBI VAAE1OS', 'TLB Invalidate by VA, All ASID, EL1, Outer Shareable' ),
    'S1_0_c8_c1_5'   : ( 'TLBI VALE1OS', 'TLB Invalidate by VA, Last level, EL1, Outer Shareable' ),
    'S1_0_c8_c1_7'   : ( 'TLBI VAALE1OS', 'TLB Invalidate by VA, All ASID, Last Level, EL1, Outer Shareable' ),
    'S1_0_c8_c2_1'   : ( 'TLBI RVAE1IS', 'TLB Range Invalidate by VA, EL1, Inner Shareable' ),
    'S1_0_c8_c2_3'   : ( 'TLBI RVAAE1IS', 'TLB Range Invalidate by VA, All ASID, EL1, Inner Shareable' ),
    'S1_0_c8_c2_5'   : ( 'TLBI RVALE1IS', 'TLB Range Invalidate by VA, Last level, EL1, Inner Shareable' ),
    'S1_0_c8_c2_7'   : ( 'TLBI RVAALE1IS', 'TLB Range Invalidate by VA, All ASID, Last Level, EL1, Inner Shareable' ),
    'S1_0_c8_c3_0'   : ( 'TLBI VMALLE1IS', 'TLB Invalidate by VMID, All at stage 1, EL1, Inner Shareable' ),
    'S1_0_c8_c3_1'   : ( 'TLBI VAE1IS', 'TLB Invalidate by VA, EL1, Inner Shareable' ),
    'S1_0_c8_c3_2'   : ( 'TLBI ASIDE1IS', 'TLB Invalidate by ASID, EL1, Inner Shareable' ),
    'S1_0_c8_c3_3'   : ( 'TLBI VAAE1IS', 'TLB Invalidate by VA, All ASID, EL1, Inner Shareable' ),
    'S1_0_c8_c3_5'   : ( 'TLBI VALE1IS', 'TLB Invalidate by VA, Last level, EL1, Inner Shareable' ),
    'S1_0_c8_c3_7'   : ( 'TLBI VAALE1IS', 'TLB Invalidate by VA, All ASID, Last Level, EL1, Inner Shareable' ),
    'S1_0_c8_c5_1'   : ( 'TLBI RVAE1OS', 'TLB Range Invalidate by VA, EL1, Outer Shareable' ),
    'S1_0_c8_c5_3'   : ( 'TLBI RVAAE1OS', 'TLB Range Invalidate by VA, All ASID, EL1, Outer Shareable' ),
    'S1_0_c8_c5_5'   : ( 'TLBI RVALE1OS', 'TLB Range Invalidate by VA, Last level, EL1, Outer Shareable' ),
    'S1_0_c8_c5_7'   : ( 'TLBI RVAALE1OS', 'TLB Range Invalidate by VA, All ASID, Last Level, EL1, Outer Shareable' ),
    'S1_0_c8_c6_1'   : ( 'TLBI RVAE1', 'TLB Range Invalidate by VA, EL1' ),
    'S1_0_c8_c6_3'   : ( 'TLBI RVAAE1', 'TLB Range Invalidate by VA, All ASID, EL1' ),
    'S1_0_c8_c6_5'   : ( 'TLBI RVALE1', 'TLB Range Invalidate by VA, Last level, EL1' ),
    'S1_0_c8_c6_7'   : ( 'TLBI RVAALE1', 'TLB Range Invalidate by VA, All ASID, Last level, EL1' ),
    'S1_0_c8_c7_0'   : ( 'TLBI VMALLE1', 'TLB Invalidate by VMID, All at stage 1, EL1' ),
    'S1_0_c8_c7_1'   : ( 'TLBI VAE1', 'TLB Invalidate by VA, EL1' ),
    'S1_0_c8_c7_2'   : ( 'TLBI ASIDE1', 'TLB Invalidate by ASID, EL1' ),
    'S1_0_c8_c7_3'   : ( 'TLBI VAAE1', 'TLB Invalidate by VA, All ASID, EL1' ),
    'S1_0_c8_c7_5'   : ( 'TLBI VALE1', 'TLB Invalidate by VA, Last level, EL1' ),
    'S1_0_c8_c7_7'   : ( 'TLBI VAALE1', 'TLB Invalidate by VA, All ASID, Last level, EL1' ),
    'S1_3_c7_c3_4'   : ( 'CFP RCTX', 'Control Flow Prediction Restriction by Context' ),
    'S1_3_c7_c3_5'   : ( 'DVP RCTX', 'Data Value Prediction Restriction by Context' ),
    'S1_3_c7_c3_7'   : ( 'CPP RCTX', 'Cache Prefetch Prediction Restriction by Context' ),
    'S1_3_c7_c4_1'   : ( 'DC ZVA', 'Data Cache Zero by VA' ),
    'S1_3_c7_c4_3'   : ( 'DC GVA', 'Data Cache set Allocation Tag by VA' ),
    'S1_3_c7_c4_4'   : ( 'DC GZVA', 'Data Cache set Allocation Tags and Zero by VA' ),
    'S1_3_c7_c5_1'   : ( 'IC IVAU', 'Instruction Cache line Invalidate by VA to PoU' ),
    'S1_3_c7_c10_1'  : ( 'DC CVAC', 'Data or unified Cache line Clean by VA to PoC' ),
    'S1_3_c7_c10_3'  : ( 'DC CGVAC', 'Data, Allocation Tag or unified Cache line Clean of Allocation Tags by VA to PoC' ),
    'S1_3_c7_c10_5'  : ( 'DC CGDVAC', 'Data, Allocation Tag or unified Cache line Clean of Allocation Tags by VA to PoC' ),
    'S1_3_c7_c11_1'  : ( 'DC CVAU', 'Data or unified Cache line Clean by VA to PoU' ),
    'S1_3_c7_c12_1'  : ( 'DC CVAP', 'Data or unified Cache line Clean by VA to PoP' ),
    'S1_3_c7_c12_3'  : ( 'DC CGVAP', 'Data, Allocation Tag or unified Cache line Clean of Allocation Tags by VA to PoP' ),
    'S1_3_c7_c12_5'  : ( 'DC CGDVAP', 'Data, Allocation Tag or unified Cache line Clean of Data and Allocation Tags by VA to PoP' ),
    'S1_3_c7_c13_1'  : ( 'DC CVADP', 'Data or unified Cache line Clean by VA to PoDP' ),
    'S1_3_c7_c13_3'  : ( 'DC CGVADP', 'Clean of Allocation Tags by VA to PoDP' ),
    'S1_3_c7_c13_5'  : ( 'DC CGDVADP', 'Data, Allocation Tag or unified Cache line Clean of Allocation Tags by VA to PoDP' ),
    'S1_3_c7_c14_1'  : ( 'DC CIVAC', 'Data or unified Cache line Clean and Invalidate by VA to PoC' ),
    'S1_3_c7_c14_3'  : ( 'DC CIGVAC', 'Data, Allocation Tag or unified Cache line Clean and Invalidate of Allocation Tags by VA to PoC' ),
    'S1_3_c7_c14_5'  : ( 'DC CIGDVAC', 'Data, Allocation Tag or unified Cache line Clean and Invalidate of Data and Allocation Tags by VA to PoC' ),
    'S1_4_c7_c8_0'   : ( 'AT S1E2R', 'Address Translate Stage 1 EL2 Read' ),
    'S1_4_c7_c8_1'   : ( 'AT S1E2W', 'Address Translate Stage 1 EL2 Write' ),
    'S1_4_c7_c8_4'   : ( 'AT S12E1R', 'Address Translate Stages 1 and 2 EL1 Read' ),
    'S1_4_c7_c8_5'   : ( 'AT S12E1W', 'Address Translate Stages 1 and 2 EL1 Write' ),
    'S1_4_c7_c8_6'   : ( 'AT S12E0R', 'Address Translate Stages 1 and 2 EL0 Read' ),
    'S1_4_c7_c8_7'   : ( 'AT S12E0W', 'Address Translate Stages 1 and 2 EL0 Write' ),
    'S1_4_c8_c0_1'   : ( 'TLBI IPAS2E1IS', 'TLB Invalidate by Intermediate Physical Address, Stage 2, EL1, Inner Shareable' ),
    'S1_4_c8_c0_2'   : ( 'TLBI RIPAS2E1IS', 'TLB Range Invalidate by Intermediate Physical Address, Stage 2, EL1, Inner Shareable' ),
    'S1_4_c8_c0_5'   : ( 'TLBI IPAS2LE1IS', 'TLB Invalidate by Intermediate Physical Address, Stage 2, Last level, EL1, Inner Shareable' ),
    'S1_4_c8_c0_6'   : ( 'TLBI RIPAS2LE1IS', 'TLB Range Invalidate by Intermediate Physical Address, Stage 2, Last level, EL1, Inner Shareable' ),
    'S1_4_c8_c1_0'   : ( 'TLBI ALLE2OS', 'TLB Invalidate All, EL2, Outer Shareable' ),
    'S1_4_c8_c1_1'   : ( 'TLBI VAE2OS', 'TLB Invalidate by VA, EL2, Outer Shareable' ),
    'S1_4_c8_c1_4'   : ( 'TLBI ALLE1OS', 'TLB Invalidate All, EL1, Outer Shareable' ),
    'S1_4_c8_c1_5'   : ( 'TLBI VALE2OS', 'TLB Invalidate by VA, Last level, EL2, Outer Shareable' ),
    'S1_4_c8_c1_6'   : ( 'TLBI VMALLS12E1OS', 'TLB Invalidate by VMID, All at Stage 1 and 2, EL1, Outer Shareable' ),
    'S1_4_c8_c2_1'   : ( 'TLBI RVAE2IS', 'TLB Range Invalidate by VA, EL2, Inner Shareable' ),
    'S1_4_c8_c2_5'   : ( 'TLBI RVALE2IS', 'TLB Range Invalidate by VA, Last level, EL2, Inner Shareable' ),
    'S1_4_c8_c3_0'   : ( 'TLBI ALLE2IS', 'TLB Invalidate All, EL2, Inner Shareable' ),
    'S1_4_c8_c3_1'   : ( 'TLBI VAE2IS', 'TLB Invalidate by VA, EL2, Inner Shareable' ),
    'S1_4_c8_c3_4'   : ( 'TLBI ALLE1IS', 'TLB Invalidate All, EL1, Inner Shareable' ),
    'S1_4_c8_c3_5'   : ( 'TLBI VALE2IS', 'TLB Invalidate by VA, Last level, EL2, Inner Shareable' ),
    'S1_4_c8_c3_6'   : ( 'TLBI VMALLS12E1IS', 'TLB Invalidate by VMID, All at Stage 1 and 2, EL1, Inner Shareable' ),
    'S1_4_c8_c4_0'   : ( 'TLBI IPAS2E1OS', 'TLB Invalidate by Intermediate Physical Address, Stage 2, EL1, Outer Shareable' ),
    'S1_4_c8_c4_1'   : ( 'TLBI IPAS2E1', 'TLB Invalidate by Intermediate Physical Address, Stage 2, EL1' ),
    'S1_4_c8_c4_2'   : ( 'TLBI RIPAS2E1', 'TLB Range Invalidate by Intermediate Physical Address, Stage 2, EL1' ),
    'S1_4_c8_c4_3'   : ( 'TLBI RIPAS2E1OS', 'TLB Range Invalidate by Intermediate Physical Address, Stage 2, EL1, Outer Shareable' ),
    'S1_4_c8_c4_4'   : ( 'TLBI IPAS2LE1OS', 'TLB Invalidate by Intermediate Physical Address, Stage 2, Last level, EL1, Outer Shareable' ),
    'S1_4_c8_c4_5'   : ( 'TLBI IPAS2LE1', 'TLB Invalidate by Intermediate Physical Address, Stage 2, Last level, EL1' ),
    'S1_4_c8_c4_6'   : ( 'TLBI RIPAS2LE1', 'TLB Range Invalidate by Intermediate Physical Address, Stage 2, Last level, EL1' ),
    'S1_4_c8_c4_7'   : ( 'TLBI RIPAS2LE1OS', 'TLB Range Invalidate by Intermediate Physical Address, Stage 2, Last level, EL1, Outer Shareable' ),
    'S1_4_c8_c5_1'   : ( 'TLBI RVAE2OS', 'TLB Range Invalidate by VA, EL2, Outer Shareable' ),
    'S1_4_c8_c5_5'   : ( 'TLBI RVALE2OS', 'TLB Range Invalidate by VA, Last level, EL2, Outer Shareable' ),
    'S1_4_c8_c6_1'   : ( 'TLBI RVAE2', 'TLB Range Invalidate by VA, EL2' ),
    'S1_4_c8_c6_5'   : ( 'TLBI RVALE2', 'TLB Range Invalidate by VA, Last level, EL2' ),
    'S1_4_c8_c7_0'   : ( 'TLBI ALLE2', 'TLB Invalidate All, EL2' ),
    'S1_4_c8_c7_1'   : ( 'TLBI VAE2', 'TLB Invalidate by VA, EL2' ),
    'S1_4_c8_c7_4'   : ( 'TLBI ALLE1', 'TLB Invalidate All, EL1' ),
    'S1_4_c8_c7_5'   : ( 'TLBI VALE2', 'TLB Invalidate by VA, Last level, EL2' ),
    'S1_4_c8_c7_6'   : ( 'TLBI VMALLS12E1', 'TLB Invalidate by VMID, All at Stage 1 and 2, EL1' ),
    'S1_6_c7_c8_0'   : ( 'AT S1E3R', 'Address Translate Stage 1 EL3 Read' ),
    'S1_6_c7_c8_1'   : ( 'AT S1E3W', 'Address Translate Stage 1 EL3 Write' ),
    'S1_6_c8_c1_0'   : ( 'TLBI ALLE3OS', 'TLB Invalidate All, EL3, Outer Shareable' ),
    'S1_6_c8_c1_1'   : ( 'TLBI VAE3OS', 'TLB Invalidate by VA, EL3, Outer Shareable' ),
    'S1_6_c8_c1_5'   : ( 'TLBI VALE3OS', 'TLB Invalidate by VA, Last level, EL3, Outer Shareable' ),
    'S1_6_c8_c2_1'   : ( 'TLBI RVAE3IS', 'TLB Range Invalidate by VA, EL3, Inner Shareable' ),
    'S1_6_c8_c2_5'   : ( 'TLBI RVALE3IS', 'TLB Range Invalidate by VA, Last level, EL3, Inner Shareable' ),
    'S1_6_c8_c3_0'   : ( 'TLBI ALLE3IS', 'TLB Invalidate All, EL3, Inner Shareable' ),
    'S1_6_c8_c3_1'   : ( 'TLBI VAE3IS', 'TLB Invalidate by VA, EL3, Inner Shareable' ),
    'S1_6_c8_c3_5'   : ( 'TLBI VALE3IS', 'TLB Invalidate by VA, Last level, EL3, Inner Shareable' ),
    'S1_6_c8_c5_1'   : ( 'TLBI RVAE3OS', 'TLB Range Invalidate by VA, EL3, Outer Shareable' ),
    'S1_6_c8_c5_5'   : ( 'TLBI RVALE3OS', 'TLB Range Invalidate by VA, Last level, EL3, Outer Shareable' ),
    'S1_6_c8_c6_1'   : ( 'TLBI RVAE3', 'TLB Range Invalidate by VA, EL3' ),
    'S1_6_c8_c6_5'   : ( 'TLBI RVALE3', 'TLB Range Invalidate by VA, Last level, EL3' ),
    'S1_6_c8_c7_0'   : ( 'TLBI ALLE3', 'TLB Invalidate All, EL3' ),
    'S1_6_c8_c7_1'   : ( 'TLBI VAE3', 'TLB Invalidate by VA, EL3' ),
    'S1_6_c8_c7_5'   : ( 'TLBI VALE3', 'TLB Invalidate by VA, Last level, EL3' ),
    'S2_0_c0_c0_2'   : ( 'OSDTRRX_EL1', 'OS Lock Data Transfer Register, Receive' ),
    'S2_0_c0_c0_4'   : ( 'DBGBVR0_EL1', 'Debug Breakpoint Value Register 0' ),
    'S2_0_c0_c0_5'   : ( 'DBGBCR0_EL1', 'Debug Breakpoint Control Register 0' ),
    'S2_0_c0_c0_6'   : ( 'DBGWVR0_EL1', 'Debug Watchpoint Value Register 0' ),
    'S2_0_c0_c0_7'   : ( 'DBGWCR0_EL1', 'Debug Watchpoint Control Register 0' ),
    'S2_0_c0_c1_4'   : ( 'DBGBVR1_EL1', 'Debug Breakpoint Value Register 1' ),
    'S2_0_c0_c1_5'   : ( 'DBGBCR1_EL1', 'Debug Breakpoint Control Register 1' ),
    'S2_0_c0_c1_6'   : ( 'DBGWVR1_EL1', 'Debug Watchpoint Value Register 1' ),
    'S2_0_c0_c1_7'   : ( 'DBGWCR1_EL1', 'Debug Watchpoint Control Register 1' ),
    'S2_0_c0_c2_0'   : ( 'MDCCINT_EL1', 'Monitor DCC Interrupt Enable Register' ),
    'S2_0_c0_c2_2'   : ( 'MDSCR_EL1', 'Monitor Debug System Control Register' ),
    'S2_0_c0_c2_4'   : ( 'DBGBVR2_EL1', 'Debug Breakpoint Value Register 2' ),
    'S2_0_c0_c2_5'   : ( 'DBGBCR2_EL1', 'Debug Breakpoint Control Register 2' ),
    'S2_0_c0_c2_6'   : ( 'DBGWVR2_EL1', 'Debug Watchpoint Value Register 2' ),
    'S2_0_c0_c2_7'   : ( 'DBGWCR2_EL1', 'Debug Watchpoint Control Register 2' ),
    'S2_0_c0_c3_2'   : ( 'OSDTRTX_EL1', 'OS Lock Data Transfer Register, Transmit' ),
    'S2_0_c0_c3_4'   : ( 'DBGBVR3_EL1', 'Debug Breakpoint Value Register 3' ),
    'S2_0_c0_c3_5'   : ( 'DBGBCR3_EL1', 'Debug Breakpoint Control Register 3' ),
    'S2_0_c0_c3_6'   : ( 'DBGWVR3_EL1', 'Debug Watchpoint Value Register 3' ),
    'S2_0_c0_c3_7'   : ( 'DBGWCR3_EL1', 'Debug Watchpoint Control Register 3' ),
    'S2_0_c0_c4_4'   : ( 'DBGBVR4_EL1', 'Debug Breakpoint Value Register 4' ),
    'S2_0_c0_c4_5'   : ( 'DBGBCR4_EL1', 'Debug Breakpoint Control Register 4' ),
    'S2_0_c0_c4_6'   : ( 'DBGWVR4_EL1', 'Debug Watchpoint Value Register 4' ),
    'S2_0_c0_c4_7'   : ( 'DBGWCR4_EL1', 'Debug Watchpoint Control Register 4' ),
    'S2_0_c0_c5_4'   : ( 'DBGBVR5_EL1', 'Debug Breakpoint Value Register 5' ),
    'S2_0_c0_c5_5'   : ( 'DBGBCR5_EL1', 'Debug Breakpoint Control Register 5' ),
    'S2_0_c0_c5_6'   : ( 'DBGWVR5_EL1', 'Debug Watchpoint Value Register 5' ),
    'S2_0_c0_c5_7'   : ( 'DBGWCR5_EL1', 'Debug Watchpoint Control Register 5' ),
    'S2_0_c0_c6_2'   : ( 'OSECCR_EL1', 'OS Lock Exception Catch Control Register' ),
    'S2_0_c0_c6_4'   : ( 'DBGBVR6_EL1', 'Debug Breakpoint Value Register 6' ),
    'S2_0_c0_c6_5'   : ( 'DBGBCR6_EL1', 'Debug Breakpoint Control Register 6' ),
    'S2_0_c0_c6_6'   : ( 'DBGWVR6_EL1', 'Debug Watchpoint Value Register 6' ),
    'S2_0_c0_c6_7'   : ( 'DBGWCR6_EL1', 'Debug Watchpoint Control Register 6' ),
    'S2_0_c0_c7_4'   : ( 'DBGBVR7_EL1', 'Debug Breakpoint Value Register 7' ),
    'S2_0_c0_c7_5'   : ( 'DBGBCR7_EL1', 'Debug Breakpoint Control Register 7' ),
    'S2_0_c0_c7_6'   : ( 'DBGWVR7_EL1', 'Debug Watchpoint Value Register 7' ),
    'S2_0_c0_c7_7'   : ( 'DBGWCR7_EL1', 'Debug Watchpoint Control Register 7' ),
    'S2_0_c0_c8_4'   : ( 'DBGBVR8_EL1', 'Debug Breakpoint Value Register 8' ),
    'S2_0_c0_c8_5'   : ( 'DBGBCR8_EL1', 'Debug Breakpoint Control Register 8' ),
    'S2_0_c0_c8_6'   : ( 'DBGWVR8_EL1', 'Debug Watchpoint Value Register 8' ),
    'S2_0_c0_c8_7'   : ( 'DBGWCR8_EL1', 'Debug Watchpoint Control Register 8' ),
    'S2_0_c0_c9_4'   : ( 'DBGBVR9_EL1', 'Debug Breakpoint Value Register 9' ),
    'S2_0_c0_c9_5'   : ( 'DBGBCR9_EL1', 'Debug Breakpoint Control Register 9' ),
    'S2_0_c0_c9_6'   : ( 'DBGWVR9_EL1', 'Debug Watchpoint Value Register 9' ),
    'S2_0_c0_c9_7'   : ( 'DBGWCR9_EL1', 'Debug Watchpoint Control Register 9' ),
    'S2_0_c0_c10_4'  : ( 'DBGBVR10_EL1', 'Debug Breakpoint Value Register 10' ),
    'S2_0_c0_c10_5'  : ( 'DBGBCR10_EL1', 'Debug Breakpoint Control Register 10' ),
    'S2_0_c0_c10_6'  : ( 'DBGWVR10_EL1', 'Debug Watchpoint Value Register 10' ),
    'S2_0_c0_c10_7'  : ( 'DBGWCR10_EL1', 'Debug Watchpoint Control Register 10' ),
    'S2_0_c0_c11_4'  : ( 'DBGBVR11_EL1', 'Debug Breakpoint Value Register 11' ),
    'S2_0_c0_c11_5'  : ( 'DBGBCR11_EL1', 'Debug Breakpoint Control Register 11' ),
    'S2_0_c0_c11_6'  : ( 'DBGWVR11_EL1', 'Debug Watchpoint Value Register 11' ),
    'S2_0_c0_c11_7'  : ( 'DBGWCR11_EL1', 'Debug Watchpoint Control Register 11' ),
    'S2_0_c0_c12_4'  : ( 'DBGBVR12_EL1', 'Debug Breakpoint Value Register 12' ),
    'S2_0_c0_c12_5'  : ( 'DBGBCR12_EL1', 'Debug Breakpoint Control Register 12' ),
    'S2_0_c0_c12_6'  : ( 'DBGWVR12_EL1', 'Debug Watchpoint Value Register 12' ),
    'S2_0_c0_c12_7'  : ( 'DBGWCR12_EL1', 'Debug Watchpoint Control Register 12' ),
    'S2_0_c0_c13_4'  : ( 'DBGBVR13_EL1', 'Debug Breakpoint Value Register 13' ),
    'S2_0_c0_c13_5'  : ( 'DBGBCR13_EL1', 'Debug Breakpoint Control Register 13' ),
    'S2_0_c0_c13_6'  : ( 'DBGWVR13_EL1', 'Debug Watchpoint Value Register 13' ),
    'S2_0_c0_c13_7'  : ( 'DBGWCR13_EL1', 'Debug Watchpoint Control Register 13' ),
    'S2_0_c0_c14_4'  : ( 'DBGBVR14_EL1', 'Debug Breakpoint Value Register 14' ),
    'S2_0_c0_c14_5'  : ( 'DBGBCR14_EL1', 'Debug Breakpoint Control Register 14' ),
    'S2_0_c0_c14_6'  : ( 'DBGWVR14_EL1', 'Debug Watchpoint Value Register 14' ),
    'S2_0_c0_c14_7'  : ( 'DBGWCR14_EL1', 'Debug Watchpoint Control Register 14' ),
    'S2_0_c0_c15_4'  : ( 'DBGBVR15_EL1', 'Debug Breakpoint Value Register 15' ),
    'S2_0_c0_c15_5'  : ( 'DBGBCR15_EL1', 'Debug Breakpoint Control Register 15' ),
    'S2_0_c0_c15_6'  : ( 'DBGWVR15_EL1', 'Debug Watchpoint Value Register 15' ),
    'S2_0_c0_c15_7'  : ( 'DBGWCR15_EL1', 'Debug Watchpoint Control Register 15' ),
    'S2_0_c1_c0_0'   : ( 'MDRAR_EL1', 'Monitor Debug ROM Address Register' ),
    'S2_0_c1_c0_4'   : ( 'OSLAR_EL1', 'OS Lock Access Register' ),
    'S2_0_c1_c1_4'   : ( 'OSLSR_EL1', 'OS Lock Status Register' ),
    'S2_0_c1_c3_4'   : ( 'OSDLR_EL1', 'OS Double Lock Register' ),
    'S2_0_c1_c4_4'   : ( 'DBGPRCR_EL1', 'Debug Power Control Register' ),
    'S2_0_c7_c8_6'   : ( 'DBGCLAIMSET_EL1', 'Debug CLAIM Tag Set register' ),
    'S2_0_c7_c9_6'   : ( 'DBGCLAIMCLR_EL1', 'Debug CLAIM Tag Clear register' ),
    'S2_0_c7_c14_6'  : ( 'DBGAUTHSTATUS_EL1', 'Debug Authentication Status register' ),
    'S2_3_c0_c1_0'   : ( 'MDCCSR_EL0', 'Monitor DCC Status Register' ),
    'S2_3_c0_c4_0'   : ( 'DBGDTR_EL0', 'Debug Data Transfer Register, half-duplex' ),
    'S2_3_c0_c5_0'   : ( 'DBGDTRRX_EL0', 'Debug Data Transfer Register, Receive' ),
    'S2_3_c0_c5_0'   : ( 'DBGDTRTX_EL0', 'Debug Data Transfer Register, Transmit' ),
    'S2_4_c0_c7_0'   : ( 'DBGVCR32_EL2', 'Debug Vector Catch Register' ),
    'S3_0_c0_c0_0'   : ( 'MIDR_EL1', 'Main ID Register' ),
    'S3_0_c0_c0_5'   : ( 'MPIDR_EL1', 'Multiprocessor Affinity Register' ),
    'S3_0_c0_c0_6'   : ( 'REVIDR_EL1', 'Revision ID Register' ),
    'S3_0_c0_c1_0'   : ( 'ID_PFR0_EL1', 'AArch32 Processor Feature Register 0' ),
    'S3_0_c0_c1_1'   : ( 'ID_PFR1_EL1', 'AArch32 Processor Feature Register 1' ),
    'S3_0_c0_c1_2'   : ( 'ID_DFR0_EL1', 'AArch32 Debug Feature Register 0' ),
    'S3_0_c0_c1_3'   : ( 'ID_AFR0_EL1', 'AArch32 Auxiliary Feature Register 0' ),
    'S3_0_c0_c1_4'   : ( 'ID_MMFR0_EL1', 'AArch32 Memory Model Feature Register 0' ),
    'S3_0_c0_c1_5'   : ( 'ID_MMFR1_EL1', 'AArch32 Memory Model Feature Register 1' ),
    'S3_0_c0_c1_6'   : ( 'ID_MMFR2_EL1', 'AArch32 Memory Model Feature Register 2' ),
    'S3_0_c0_c1_7'   : ( 'ID_MMFR3_EL1', 'AArch32 Memory Model Feature Register 3' ),
    'S3_0_c0_c2_0'   : ( 'ID_ISAR0_EL1', 'AArch32 Instruction Set Attribute Register 0' ),
    'S3_0_c0_c2_1'   : ( 'ID_ISAR1_EL1', 'AArch32 Instruction Set Attribute Register 1' ),
    'S3_0_c0_c2_2'   : ( 'ID_ISAR2_EL1', 'AArch32 Instruction Set Attribute Register 2' ),
    'S3_0_c0_c2_3'   : ( 'ID_ISAR3_EL1', 'AArch32 Instruction Set Attribute Register 3' ),
    'S3_0_c0_c2_4'   : ( 'ID_ISAR4_EL1', 'AArch32 Instruction Set Attribute Register 4' ),
    'S3_0_c0_c2_5'   : ( 'ID_ISAR5_EL1', 'AArch32 Instruction Set Attribute Register 5' ),
    'S3_0_c0_c2_6'   : ( 'ID_MMFR4_EL1', 'AArch32 Memory Model Feature Register 4' ),
    'S3_0_c0_c2_7'   : ( 'ID_ISAR6_EL1', 'AArch32 Instruction Set Attribute Register 6' ),
    'S3_0_c0_c3_0'   : ( 'MVFR0_EL1', 'AArch32 Media and VFP Feature Register 0' ),
    'S3_0_c0_c3_1'   : ( 'MVFR1_EL1', 'AArch32 Media and VFP Feature Register 1' ),
    'S3_0_c0_c3_2'   : ( 'MVFR2_EL1', 'AArch32 Media and VFP Feature Register 2' ),
    'S3_0_c0_c3_4'   : ( 'ID_PFR2_EL1', 'AArch32 Processor Feature Register 2' ),
    'S3_0_c0_c3_5'   : ( 'ID_DFR1_EL1', 'Debug Feature Register 1' ),
    'S3_0_c0_c3_6'   : ( 'ID_MMFR5_EL1', 'AArch32 Memory Model Feature Register 5' ),
    'S3_0_c0_c4_0'   : ( 'ID_AA64PFR0_EL1', 'AArch64 Processor Feature Register 0' ),
    'S3_0_c0_c4_1'   : ( 'ID_AA64PFR1_EL1', 'AArch64 Processor Feature Register 1' ),
    'S3_0_c0_c4_4'   : ( 'ID_AA64ZFR0_EL1', 'SVE Feature ID register 0' ),
    'S3_0_c0_c5_0'   : ( 'ID_AA64DFR0_EL1', 'AArch64 Debug Feature Register 0' ),
    'S3_0_c0_c5_1'   : ( 'ID_AA64DFR1_EL1', 'AArch64 Debug Feature Register 1' ),
    'S3_0_c0_c5_4'   : ( 'ID_AA64AFR0_EL1', 'AArch64 Auxiliary Feature Register 0' ),
    'S3_0_c0_c5_5'   : ( 'ID_AA64AFR1_EL1', 'AArch64 Auxiliary Feature Register 1' ),
    'S3_0_c0_c6_0'   : ( 'ID_AA64ISAR0_EL1', 'AArch64 Instruction Set Attribute Register 0' ),
    'S3_0_c0_c6_1'   : ( 'ID_AA64ISAR1_EL1', 'AArch64 Instruction Set Attribute Register 1' ),
    'S3_0_c0_c7_0'   : ( 'ID_AA64MMFR0_EL1', 'AArch64 Memory Model Feature Register 0' ),
    'S3_0_c0_c7_1'   : ( 'ID_AA64MMFR1_EL1', 'AArch64 Memory Model Feature Register 1' ),
    'S3_0_c0_c7_2'   : ( 'ID_AA64MMFR2_EL1', 'AArch64 Memory Model Feature Register 2' ),
    'S3_0_c1_c0_0'   : ( 'SCTLR_EL1', 'System Control Register (EL1)' ),
    'S3_0_c1_c0_1'   : ( 'ACTLR_EL1', 'Auxiliary Control Register (EL1)' ),
    'S3_0_c1_c0_2'   : ( 'CPACR_EL1', 'Architectural Feature Access Control Register' ),
    'S3_0_c1_c0_5'   : ( 'RGSR_EL1', 'Random Allocation Tag Seed Register.' ),
    'S3_0_c1_c0_6'   : ( 'GCR_EL1', 'Tag Control Register.' ),
    'S3_0_c1_c2_0'   : ( 'ZCR_EL1', 'SVE Control Register for EL1' ),
    'S3_0_c1_c2_1'   : ( 'TRFCR_EL1', 'Trace Filter Control Register (EL1)' ),
    'S3_0_c2_c0_0'   : ( 'TTBR0_EL1', 'Translation Table Base Register 0 (EL1)' ),
    'S3_0_c2_c0_1'   : ( 'TTBR1_EL1', 'Translation Table Base Register 1 (EL1)' ),
    'S3_0_c2_c0_2'   : ( 'TCR_EL1', 'Translation Control Register (EL1)' ),
    'S3_0_c2_c1_0'   : ( 'APIAKeyLo_EL1', 'Pointer Authentication Key A for Instruction (bits[63:0]) ' ),
    'S3_0_c2_c1_1'   : ( 'APIAKeyHi_EL1', 'Pointer Authentication Key A for Instruction (bits[127:64]) ' ),
    'S3_0_c2_c1_2'   : ( 'APIBKeyLo_EL1', 'Pointer Authentication Key B for Instruction (bits[63:0]) ' ),
    'S3_0_c2_c1_3'   : ( 'APIBKeyHi_EL1', 'Pointer Authentication Key B for Instruction (bits[127:64]) ' ),
    'S3_0_c2_c2_0'   : ( 'APDAKeyLo_EL1', 'Pointer Authentication Key A for Data (bits[63:0]) ' ),
    'S3_0_c2_c2_1'   : ( 'APDAKeyHi_EL1', 'Pointer Authentication Key A for Data (bits[127:64]) ' ),
    'S3_0_c2_c2_2'   : ( 'APDBKeyLo_EL1', 'Pointer Authentication Key B for Data (bits[63:0]) ' ),
    'S3_0_c2_c2_3'   : ( 'APDBKeyHi_EL1', 'Pointer Authentication Key B for Data (bits[127:64]) ' ),
    'S3_0_c2_c3_0'   : ( 'APGAKeyLo_EL1', 'Pointer Authentication Key A for Code (bits[63:0]) ' ),
    'S3_0_c2_c3_1'   : ( 'APGAKeyHi_EL1', 'Pointer Authentication Key A for Code (bits[127:64]) ' ),
    'S3_0_c4_c0_0'   : ( 'SPSR_EL1', 'Saved Program Status Register (EL1)' ),
    'S3_0_c4_c0_1'   : ( 'ELR_EL1', 'Exception Link Register (EL1)' ),
    'S3_0_c4_c1_0'   : ( 'SP_EL0', 'Stack Pointer (EL0)' ),
    'S3_0_c4_c2_0'   : ( 'SPSel', 'Stack Pointer Select' ),
    'S3_0_c4_c2_2'   : ( 'CurrentEL', 'Current Exception Level' ),
    'S3_0_c4_c2_3'   : ( 'PAN', 'Privileged Access Never' ),
    'S3_0_c4_c2_4'   : ( 'UAO', 'User Access Override' ),
    'S3_0_c4_c6_0'   : ( 'ICC_PMR_EL1', 'Interrupt Controller Interrupt Priority Mask Register' ),
    'S3_0_c4_c6_0'   : ( 'ICV_PMR_EL1', 'Interrupt Controller Virtual Interrupt Priority Mask Register' ),
    'S3_0_c5_c1_0'   : ( 'AFSR0_EL1', 'Auxiliary Fault Status Register 0 (EL1)' ),
    'S3_0_c5_c1_1'   : ( 'AFSR1_EL1', 'Auxiliary Fault Status Register 1 (EL1)' ),
    'S3_0_c5_c2_0'   : ( 'ESR_EL1', 'Exception Syndrome Register (EL1)' ),
    'S3_0_c5_c3_0'   : ( 'ERRIDR_EL1', 'Error Record ID Register' ),
    'S3_0_c5_c3_1'   : ( 'ERRSELR_EL1', 'Error Record Select Register' ),
    'S3_0_c5_c4_0'   : ( 'ERXFR_EL1', 'Selected Error Record Feature Register' ),
    'S3_0_c5_c4_1'   : ( 'ERXCTLR_EL1', 'Selected Error Record Control Register' ),
    'S3_0_c5_c4_2'   : ( 'ERXSTATUS_EL1', 'Selected Error Record Primary Status Register' ),
    'S3_0_c5_c4_3'   : ( 'ERXADDR_EL1', 'Selected Error Record Address Register' ),
    'S3_0_c5_c4_4'   : ( 'ERXPFGF_EL1', 'Selected Pseudo-fault Generation Feature register' ),
    'S3_0_c5_c4_5'   : ( 'ERXPFGCTL_EL1', 'Selected Pseudo-fault Generation Control register' ),
    'S3_0_c5_c4_6'   : ( 'ERXPFGCDN_EL1', 'Selected Pseudo-fault Generation Countdown register' ),
    'S3_0_c5_c5_0'   : ( 'ERXMISC0_EL1', 'Selected Error Record Miscellaneous Register 0' ),
    'S3_0_c5_c5_1'   : ( 'ERXMISC1_EL1', 'Selected Error Record Miscellaneous Register 1' ),
    'S3_0_c5_c5_2'   : ( 'ERXMISC2_EL1', 'Selected Error Record Miscellaneous Register 2' ),
    'S3_0_c5_c5_3'   : ( 'ERXMISC3_EL1', 'Selected Error Record Miscellaneous Register 3' ),
    'S3_0_c5_c6_0'   : ( 'TFSR_EL1', 'Tag Fault Status Register (EL1)' ),
    'S3_0_c5_c6_1'   : ( 'TFSRE0_EL1', 'Tag Fault Status Register (EL0).' ),
    'S3_0_c6_c0_0'   : ( 'FAR_EL1', 'Fault Address Register (EL1)' ),
    'S3_0_c7_c4_0'   : ( 'PAR_EL1', 'Physical Address Register' ),
    'S3_0_c9_c9_0'   : ( 'PMSCR_EL1', 'Statistical Profiling Control Register (EL1)' ),
    'S3_0_c9_c9_2'   : ( 'PMSICR_EL1', 'Sampling Interval Counter Register' ),
    'S3_0_c9_c9_3'   : ( 'PMSIRR_EL1', 'Sampling Interval Reload Register' ),
    'S3_0_c9_c9_4'   : ( 'PMSFCR_EL1', 'Sampling Filter Control Register' ),
    'S3_0_c9_c9_5'   : ( 'PMSEVFR_EL1', 'Sampling Event Filter Register' ),
    'S3_0_c9_c9_6'   : ( 'PMSLATFR_EL1', 'Sampling Latency Filter Register' ),
    'S3_0_c9_c9_7'   : ( 'PMSIDR_EL1', 'Sampling Profiling ID Register' ),
    'S3_0_c9_c10_0'  : ( 'PMBLIMITR_EL1', 'Profiling Buffer Limit Address Register' ),
    'S3_0_c9_c10_1'  : ( 'PMBPTR_EL1', 'Profiling Buffer Write Pointer Register' ),
    'S3_0_c9_c10_3'  : ( 'PMBSR_EL1', 'Profiling Buffer Status/syndrome Register' ),
    'S3_0_c9_c10_7'  : ( 'PMBIDR_EL1', 'Profiling Buffer ID Register' ),
    'S3_0_c9_c14_1'  : ( 'PMINTENSET_EL1', 'Performance Monitors Interrupt Enable Set register' ),
    'S3_0_c9_c14_2'  : ( 'PMINTENCLR_EL1', 'Performance Monitors Interrupt Enable Clear register' ),
    'S3_0_c9_c14_6'  : ( 'PMMIR_EL1', 'Performance Monitors Machine Identification Register' ),
    'S3_0_c10_c2_0'  : ( 'MAIR_EL1', 'Memory Attribute Indirection Register (EL1)' ),
    'S3_0_c10_c3_0'  : ( 'AMAIR_EL1', 'Auxiliary Memory Attribute Indirection Register (EL1)' ),
    'S3_0_c10_c4_0'  : ( 'LORSA_EL1', 'LORegion Start Address (EL1)' ),
    'S3_0_c10_c4_1'  : ( 'LOREA_EL1', 'LORegion End Address (EL1)' ),
    'S3_0_c10_c4_2'  : ( 'LORN_EL1', 'LORegion Number (EL1)' ),
    'S3_0_c10_c4_3'  : ( 'LORC_EL1', 'LORegion Control (EL1)' ),
    'S3_0_c10_c4_4'  : ( 'MPAMIDR_EL1', 'MPAM ID Register (EL1)' ),
    'S3_0_c10_c4_7'  : ( 'LORID_EL1', 'LORegionID (EL1)' ),
    'S3_0_c10_c5_0'  : ( 'MPAM1_EL1', 'MPAM1 Register (EL1)' ),
    'S3_0_c10_c5_1'  : ( 'MPAM0_EL1', 'MPAM0 Register (EL1)' ),
    'S3_0_c12_c0_0'  : ( 'VBAR_EL1', 'Vector Base Address Register (EL1)' ),
    'S3_0_c12_c0_1'  : ( 'RVBAR_EL1', 'Reset Vector Base Address Register (if EL2 and EL3 not implemented)' ),
    'S3_0_c12_c0_2'  : ( 'RMR_EL1', 'Reset Management Register (EL1)' ),
    'S3_0_c12_c1_0'  : ( 'ISR_EL1', 'Interrupt Status Register' ),
    'S3_0_c12_c1_1'  : ( 'DISR_EL1', 'Deferred Interrupt Status Register' ),
    'S3_0_c12_c8_0'  : ( 'ICC_IAR0_EL1', 'Interrupt Controller Interrupt Acknowledge Register 0' ),
    'S3_0_c12_c8_0'  : ( 'ICV_IAR0_EL1', 'Interrupt Controller Virtual Interrupt Acknowledge Register 0' ),
    'S3_0_c12_c8_1'  : ( 'ICC_EOIR0_EL1', 'Interrupt Controller End Of Interrupt Register 0' ),
    'S3_0_c12_c8_1'  : ( 'ICV_EOIR0_EL1', 'Interrupt Controller Virtual End Of Interrupt Register 0' ),
    'S3_0_c12_c8_2'  : ( 'ICC_HPPIR0_EL1', 'Interrupt Controller Highest Priority Pending Interrupt Register 0' ),
    'S3_0_c12_c8_2'  : ( 'ICV_HPPIR0_EL1', 'Interrupt Controller Virtual Highest Priority Pending Interrupt Register 0' ),
    'S3_0_c12_c8_3'  : ( 'ICC_BPR0_EL1', 'Interrupt Controller Binary Point Register 0' ),
    'S3_0_c12_c8_3'  : ( 'ICV_BPR0_EL1', 'Interrupt Controller Virtual Binary Point Register 0' ),
    'S3_0_c12_c8_4'  : ( 'ICC_AP0R0_EL1', 'Interrupt Controller Active Priorities Group 0 Register 0' ),
    'S3_0_c12_c8_4'  : ( 'ICV_AP0R0_EL1', 'Interrupt Controller Virtual Active Priorities Group 0 Register 0' ),
    'S3_0_c12_c8_5'  : ( 'ICC_AP0R1_EL1', 'Interrupt Controller Active Priorities Group 0 Register 1' ),
    'S3_0_c12_c8_5'  : ( 'ICV_AP0R1_EL1', 'Interrupt Controller Virtual Active Priorities Group 0 Register 1' ),
    'S3_0_c12_c8_6'  : ( 'ICC_AP0R2_EL1', 'Interrupt Controller Active Priorities Group 0 Register 2' ),
    'S3_0_c12_c8_6'  : ( 'ICV_AP0R2_EL1', 'Interrupt Controller Virtual Active Priorities Group 0 Register 2' ),
    'S3_0_c12_c8_7'  : ( 'ICC_AP0R3_EL1', 'Interrupt Controller Active Priorities Group 0 Register 3' ),
    'S3_0_c12_c8_7'  : ( 'ICV_AP0R3_EL1', 'Interrupt Controller Virtual Active Priorities Group 0 Register 3' ),
    'S3_0_c12_c9_0'  : ( 'ICC_AP1R0_EL1', 'Interrupt Controller Active Priorities Group 1 Register 0' ),
    'S3_0_c12_c9_0'  : ( 'ICV_AP1R0_EL1', 'Interrupt Controller Virtual Active Priorities Group 1 Register 0' ),
    'S3_0_c12_c9_1'  : ( 'ICC_AP1R1_EL1', 'Interrupt Controller Active Priorities Group 1 Register 1' ),
    'S3_0_c12_c9_1'  : ( 'ICV_AP1R1_EL1', 'Interrupt Controller Virtual Active Priorities Group 1 Register 1' ),
    'S3_0_c12_c9_2'  : ( 'ICC_AP1R2_EL1', 'Interrupt Controller Active Priorities Group 1 Register 2' ),
    'S3_0_c12_c9_2'  : ( 'ICV_AP1R2_EL1', 'Interrupt Controller Virtual Active Priorities Group 1 Register 2' ),
    'S3_0_c12_c9_3'  : ( 'ICC_AP1R3_EL1', 'Interrupt Controller Active Priorities Group 1 Register 3' ),
    'S3_0_c12_c9_3'  : ( 'ICV_AP1R3_EL1', 'Interrupt Controller Virtual Active Priorities Group 1 Register 3' ),
    'S3_0_c12_c11_1' : ( 'ICC_DIR_EL1', 'Interrupt Controller Deactivate Interrupt Register' ),
    'S3_0_c12_c11_1' : ( 'ICV_DIR_EL1', 'Interrupt Controller Deactivate Virtual Interrupt Register' ),
    'S3_0_c12_c11_3' : ( 'ICC_RPR_EL1', 'Interrupt Controller Running Priority Register' ),
    'S3_0_c12_c11_3' : ( 'ICV_RPR_EL1', 'Interrupt Controller Virtual Running Priority Register' ),
    'S3_0_c12_c11_5' : ( 'ICC_SGI1R_EL1', 'Interrupt Controller Software Generated Interrupt Group 1 Register' ),
    'S3_0_c12_c11_6' : ( 'ICC_ASGI1R_EL1', 'Interrupt Controller Alias Software Generated Interrupt Group 1 Register' ),
    'S3_0_c12_c11_7' : ( 'ICC_SGI0R_EL1', 'Interrupt Controller Software Generated Interrupt Group 0 Register' ),
    'S3_0_c12_c12_0' : ( 'ICC_IAR1_EL1', 'Interrupt Controller Interrupt Acknowledge Register 1' ),
    'S3_0_c12_c12_0' : ( 'ICV_IAR1_EL1', 'Interrupt Controller Virtual Interrupt Acknowledge Register 1' ),
    'S3_0_c12_c12_1' : ( 'ICC_EOIR1_EL1', 'Interrupt Controller End Of Interrupt Register 1' ),
    'S3_0_c12_c12_1' : ( 'ICV_EOIR1_EL1', 'Interrupt Controller Virtual End Of Interrupt Register 1' ),
    'S3_0_c12_c12_2' : ( 'ICC_HPPIR1_EL1', 'Interrupt Controller Highest Priority Pending Interrupt Register 1' ),
    'S3_0_c12_c12_2' : ( 'ICV_HPPIR1_EL1', 'Interrupt Controller Virtual Highest Priority Pending Interrupt Register 1' ),
    'S3_0_c12_c12_3' : ( 'ICC_BPR1_EL1', 'Interrupt Controller Binary Point Register 1' ),
    'S3_0_c12_c12_3' : ( 'ICV_BPR1_EL1', 'Interrupt Controller Virtual Binary Point Register 1' ),
    'S3_0_c12_c12_4' : ( 'ICC_CTLR_EL1', 'Interrupt Controller Control Register (EL1)' ),
    'S3_0_c12_c12_4' : ( 'ICV_CTLR_EL1', 'Interrupt Controller Virtual Control Register' ),
    'S3_0_c12_c12_5' : ( 'ICC_SRE_EL1', 'Interrupt Controller System Register Enable register (EL1)' ),
    'S3_0_c12_c12_6' : ( 'ICC_IGRPEN0_EL1', 'Interrupt Controller Interrupt Group 0 Enable register' ),
    'S3_0_c12_c12_6' : ( 'ICV_IGRPEN0_EL1', 'Interrupt Controller Virtual Interrupt Group 0 Enable register' ),
    'S3_0_c12_c12_7' : ( 'ICC_IGRPEN1_EL1', 'Interrupt Controller Interrupt Group 1 Enable register' ),
    'S3_0_c12_c12_7' : ( 'ICV_IGRPEN1_EL1', 'Interrupt Controller Virtual Interrupt Group 1 Enable register' ),
    'S3_0_c13_c0_1'  : ( 'CONTEXTIDR_EL1', 'Context ID Register (EL1)' ),
    'S3_0_c13_c0_4'  : ( 'TPIDR_EL1', 'EL1 Software Thread ID Register' ),
    'S3_0_c13_c0_7'  : ( 'SCXTNUM_EL1', 'EL1 Read/Write Software Context Number' ),
    'S3_0_c14_c1_0'  : ( 'CNTKCTL_EL1', 'Counter-timer Kernel Control register' ),
    'S3_1_c0_c0_0'   : ( 'CCSIDR_EL1', 'Current Cache Size ID Register' ),
    'S3_1_c0_c0_1'   : ( 'CLIDR_EL1', 'Cache Level ID Register' ),
    'S3_1_c0_c0_2'   : ( 'CCSIDR2_EL1', 'Current Cache Size ID Register 2' ),
    'S3_1_c0_c0_4'   : ( 'GMID_EL1', ' Multiple tag transfer ID register' ),
    'S3_1_c0_c0_7'   : ( 'AIDR_EL1', 'Auxiliary ID Register' ),
    'S3_2_c0_c0_0'   : ( 'CSSELR_EL1', 'Cache Size Selection Register' ),
    'S3_3_c0_c0_1'   : ( 'CTR_EL0', 'Cache Type Register' ),
    'S3_3_c0_c0_7'   : ( 'DCZID_EL0', 'Data Cache Zero ID register' ),
    'S3_3_c2_c4_0'   : ( 'RNDR', 'Random Number' ),
    'S3_3_c2_c4_1'   : ( 'RNDRRS', 'Reseeded Random Number' ),
    'S3_3_c4_c2_0'   : ( 'NZCV', 'Condition Flags' ),
    'S3_3_c4_c2_1'   : ( 'DAIF', 'Interrupt Mask Bits' ),
    'S3_3_c4_c2_5'   : ( 'DIT', 'Data Independent Timing' ),
    'S3_3_c4_c2_6'   : ( 'SSBS', 'Speculative Store Bypass Safe' ),
    'S3_3_c4_c2_7'   : ( 'TCO', 'Tag Check Override' ),
    'S3_3_c4_c4_0'   : ( 'FPCR', 'Floating-point Control Register' ),
    'S3_3_c4_c4_1'   : ( 'FPSR', 'Floating-point Status Register' ),
    'S3_3_c4_c5_0'   : ( 'DSPSR_EL0', 'Debug Saved Program Status Register' ),
    'S3_3_c4_c5_1'   : ( 'DLR_EL0', 'Debug Link Register' ),
    'S3_3_c9_c12_0'  : ( 'PMCR_EL0', 'Performance Monitors Control Register' ),
    'S3_3_c9_c12_1'  : ( 'PMCNTENSET_EL0', 'Performance Monitors Count Enable Set register' ),
    'S3_3_c9_c12_2'  : ( 'PMCNTENCLR_EL0', 'Performance Monitors Count Enable Clear register' ),
    'S3_3_c9_c12_3'  : ( 'PMOVSCLR_EL0', 'Performance Monitors Overflow Flag Status Clear Register' ),
    'S3_3_c9_c12_4'  : ( 'PMSWINC_EL0', 'Performance Monitors Software Increment register' ),
    'S3_3_c9_c12_5'  : ( 'PMSELR_EL0', 'Performance Monitors Event Counter Selection Register' ),
    'S3_3_c9_c12_6'  : ( 'PMCEID0_EL0', 'Performance Monitors Common Event Identification register 0' ),
    'S3_3_c9_c12_7'  : ( 'PMCEID1_EL0', 'Performance Monitors Common Event Identification register 1' ),
    'S3_3_c9_c13_0'  : ( 'PMCCNTR_EL0', 'Performance Monitors Cycle Count Register' ),
    'S3_3_c9_c13_1'  : ( 'PMXEVTYPER_EL0', 'Performance Monitors Selected Event Type Register' ),
    'S3_3_c9_c13_2'  : ( 'PMXEVCNTR_EL0', 'Performance Monitors Selected Event Count Register' ),
    'S3_3_c9_c14_0'  : ( 'PMUSERENR_EL0', 'Performance Monitors User Enable Register' ),
    'S3_3_c9_c14_3'  : ( 'PMOVSSET_EL0', 'Performance Monitors Overflow Flag Status Set register' ),
    'S3_3_c13_c0_2'  : ( 'TPIDR_EL0', 'EL0 Read/Write Software Thread ID Register' ),
    'S3_3_c13_c0_3'  : ( 'TPIDRRO_EL0', 'EL0 Read-Only Software Thread ID Register' ),
    'S3_3_c13_c0_7'  : ( 'SCXTNUM_EL0', 'EL0 Read/Write Software Context Number' ),
    'S3_3_c13_c2_0'  : ( 'AMCR_EL0', 'Activity Monitors Control Register' ),
    'S3_3_c13_c2_1'  : ( 'AMCFGR_EL0', 'Activity Monitors Configuration Register' ),
    'S3_3_c13_c2_2'  : ( 'AMCGCR_EL0', 'Activity Monitors Counter Group Configuration Register' ),
    'S3_3_c13_c2_3'  : ( 'AMUSERENR_EL0', 'Activity Monitors User Enable Register' ),
    'S3_3_c13_c2_4'  : ( 'AMCNTENCLR0_EL0', 'Activity Monitors Count Enable Clear Register 0' ),
    'S3_3_c13_c2_5'  : ( 'AMCNTENSET0_EL0', 'Activity Monitors Count Enable Set Register 0' ),
    'S3_3_c13_c2_6'  : ( 'AMCG1IDR_EL0', 'Activity Monitors Counter Group 1 Identification Register' ),
    'S3_3_c13_c3_0'  : ( 'AMCNTENCLR1_EL0', 'Activity Monitors Count Enable Clear Register 1' ),
    'S3_3_c13_c3_1'  : ( 'AMCNTENSET1_EL0', 'Activity Monitors Count Enable Set Register 1' ),
    'S3_3_c13_c4_0'  : ( 'AMEVCNTR00_EL0', 'Activity Monitors Event Counter Register 0 0' ),
    'S3_3_c13_c4_1'  : ( 'AMEVCNTR01_EL0', 'Activity Monitors Event Counter Register 0 1' ),
    'S3_3_c13_c4_2'  : ( 'AMEVCNTR02_EL0', 'Activity Monitors Event Counter Register 0 2' ),
    'S3_3_c13_c4_3'  : ( 'AMEVCNTR03_EL0', 'Activity Monitors Event Counter Register 0 3' ),
    'S3_3_c13_c4_4'  : ( 'AMEVCNTR04_EL0', 'Activity Monitors Event Counter Register 0 4' ),
    'S3_3_c13_c4_5'  : ( 'AMEVCNTR05_EL0', 'Activity Monitors Event Counter Register 0 5' ),
    'S3_3_c13_c4_6'  : ( 'AMEVCNTR06_EL0', 'Activity Monitors Event Counter Register 0 6' ),
    'S3_3_c13_c4_7'  : ( 'AMEVCNTR07_EL0', 'Activity Monitors Event Counter Register 0 7' ),
    'S3_3_c13_c5_0'  : ( 'AMEVCNTR08_EL0', 'Activity Monitors Event Counter Register 0 8' ),
    'S3_3_c13_c5_1'  : ( 'AMEVCNTR09_EL0', 'Activity Monitors Event Counter Register 0 9' ),
    'S3_3_c13_c5_2'  : ( 'AMEVCNTR010_EL0', 'Activity Monitors Event Counter Register 0 10' ),
    'S3_3_c13_c5_3'  : ( 'AMEVCNTR011_EL0', 'Activity Monitors Event Counter Register 0 11' ),
    'S3_3_c13_c5_4'  : ( 'AMEVCNTR012_EL0', 'Activity Monitors Event Counter Register 0 12' ),
    'S3_3_c13_c5_5'  : ( 'AMEVCNTR013_EL0', 'Activity Monitors Event Counter Register 0 13' ),
    'S3_3_c13_c5_6'  : ( 'AMEVCNTR014_EL0', 'Activity Monitors Event Counter Register 0 14' ),
    'S3_3_c13_c5_7'  : ( 'AMEVCNTR015_EL0', 'Activity Monitors Event Counter Register 0 15' ),
    'S3_3_c13_c6_0'  : ( 'AMEVTYPER00_EL0', 'Activity Monitors Event Type Register 0 0' ),
    'S3_3_c13_c6_1'  : ( 'AMEVTYPER01_EL0', 'Activity Monitors Event Type Register 0 1' ),
    'S3_3_c13_c6_2'  : ( 'AMEVTYPER02_EL0', 'Activity Monitors Event Type Register 0 2' ),
    'S3_3_c13_c6_3'  : ( 'AMEVTYPER03_EL0', 'Activity Monitors Event Type Register 0 3' ),
    'S3_3_c13_c6_4'  : ( 'AMEVTYPER04_EL0', 'Activity Monitors Event Type Register 0 4' ),
    'S3_3_c13_c6_5'  : ( 'AMEVTYPER05_EL0', 'Activity Monitors Event Type Register 0 5' ),
    'S3_3_c13_c6_6'  : ( 'AMEVTYPER06_EL0', 'Activity Monitors Event Type Register 0 6' ),
    'S3_3_c13_c6_7'  : ( 'AMEVTYPER07_EL0', 'Activity Monitors Event Type Register 0 7' ),
    'S3_3_c13_c7_0'  : ( 'AMEVTYPER08_EL0', 'Activity Monitors Event Type Register 0 8' ),
    'S3_3_c13_c7_1'  : ( 'AMEVTYPER09_EL0', 'Activity Monitors Event Type Register 0 9' ),
    'S3_3_c13_c7_2'  : ( 'AMEVTYPER010_EL0', 'Activity Monitors Event Type Register 0 10' ),
    'S3_3_c13_c7_3'  : ( 'AMEVTYPER011_EL0', 'Activity Monitors Event Type Register 0 11' ),
    'S3_3_c13_c7_4'  : ( 'AMEVTYPER012_EL0', 'Activity Monitors Event Type Register 0 12' ),
    'S3_3_c13_c7_5'  : ( 'AMEVTYPER013_EL0', 'Activity Monitors Event Type Register 0 13' ),
    'S3_3_c13_c7_6'  : ( 'AMEVTYPER014_EL0', 'Activity Monitors Event Type Register 0 14' ),
    'S3_3_c13_c7_7'  : ( 'AMEVTYPER015_EL0', 'Activity Monitors Event Type Register 0 15' ),
    'S3_3_c13_c12_0' : ( 'AMEVCNTR10_EL0', 'Activity Monitors Event Counter Register 1 0' ),
    'S3_3_c13_c12_1' : ( 'AMEVCNTR11_EL0', 'Activity Monitors Event Counter Register 1 1' ),
    'S3_3_c13_c12_2' : ( 'AMEVCNTR12_EL0', 'Activity Monitors Event Counter Register 1 2' ),
    'S3_3_c13_c12_3' : ( 'AMEVCNTR13_EL0', 'Activity Monitors Event Counter Register 1 3' ),
    'S3_3_c13_c12_4' : ( 'AMEVCNTR14_EL0', 'Activity Monitors Event Counter Register 1 4' ),
    'S3_3_c13_c12_5' : ( 'AMEVCNTR15_EL0', 'Activity Monitors Event Counter Register 1 5' ),
    'S3_3_c13_c12_6' : ( 'AMEVCNTR16_EL0', 'Activity Monitors Event Counter Register 1 6' ),
    'S3_3_c13_c12_7' : ( 'AMEVCNTR17_EL0', 'Activity Monitors Event Counter Register 1 7' ),
    'S3_3_c13_c13_0' : ( 'AMEVCNTR18_EL0', 'Activity Monitors Event Counter Register 1 8' ),
    'S3_3_c13_c13_1' : ( 'AMEVCNTR19_EL0', 'Activity Monitors Event Counter Register 1 9' ),
    'S3_3_c13_c13_2' : ( 'AMEVCNTR110_EL0', 'Activity Monitors Event Counter Register 1 10' ),
    'S3_3_c13_c13_3' : ( 'AMEVCNTR111_EL0', 'Activity Monitors Event Counter Register 1 11' ),
    'S3_3_c13_c13_4' : ( 'AMEVCNTR112_EL0', 'Activity Monitors Event Counter Register 1 12' ),
    'S3_3_c13_c13_5' : ( 'AMEVCNTR113_EL0', 'Activity Monitors Event Counter Register 1 13' ),
    'S3_3_c13_c13_6' : ( 'AMEVCNTR114_EL0', 'Activity Monitors Event Counter Register 1 14' ),
    'S3_3_c13_c13_7' : ( 'AMEVCNTR115_EL0', 'Activity Monitors Event Counter Register 1 15' ),
    'S3_3_c13_c14_0' : ( 'AMEVTYPER10_EL0', 'Activity Monitors Event Type Register 1 0' ),
    'S3_3_c13_c14_1' : ( 'AMEVTYPER11_EL0', 'Activity Monitors Event Type Register 1 1' ),
    'S3_3_c13_c14_2' : ( 'AMEVTYPER12_EL0', 'Activity Monitors Event Type Register 1 2' ),
    'S3_3_c13_c14_3' : ( 'AMEVTYPER13_EL0', 'Activity Monitors Event Type Register 1 3' ),
    'S3_3_c13_c14_4' : ( 'AMEVTYPER14_EL0', 'Activity Monitors Event Type Register 1 4' ),
    'S3_3_c13_c14_5' : ( 'AMEVTYPER15_EL0', 'Activity Monitors Event Type Register 1 5' ),
    'S3_3_c13_c14_6' : ( 'AMEVTYPER16_EL0', 'Activity Monitors Event Type Register 1 6' ),
    'S3_3_c13_c14_7' : ( 'AMEVTYPER17_EL0', 'Activity Monitors Event Type Register 1 7' ),
    'S3_3_c13_c15_0' : ( 'AMEVTYPER18_EL0', 'Activity Monitors Event Type Register 1 8' ),
    'S3_3_c13_c15_1' : ( 'AMEVTYPER19_EL0', 'Activity Monitors Event Type Register 1 9' ),
    'S3_3_c13_c15_2' : ( 'AMEVTYPER110_EL0', 'Activity Monitors Event Type Register 1 10' ),
    'S3_3_c13_c15_3' : ( 'AMEVTYPER111_EL0', 'Activity Monitors Event Type Register 1 11' ),
    'S3_3_c13_c15_4' : ( 'AMEVTYPER112_EL0', 'Activity Monitors Event Type Register 1 12' ),
    'S3_3_c13_c15_5' : ( 'AMEVTYPER113_EL0', 'Activity Monitors Event Type Register 1 13' ),
    'S3_3_c13_c15_6' : ( 'AMEVTYPER114_EL0', 'Activity Monitors Event Type Register 1 14' ),
    'S3_3_c13_c15_7' : ( 'AMEVTYPER115_EL0', 'Activity Monitors Event Type Register 1 15' ),
    'S3_3_c14_c0_0'  : ( 'CNTFRQ_EL0', 'Counter-timer Frequency register' ),
    'S3_3_c14_c0_1'  : ( 'CNTPCT_EL0', 'Counter-timer Physical Count register' ),
    'S3_3_c14_c0_2'  : ( 'CNTVCT_EL0', 'Counter-timer Virtual Count register' ),
    'S3_3_c14_c0_5'  : ( 'CNTPCTSS_EL0', 'Counter-timer Self-Synchronized Physical Count register' ),
    'S3_3_c14_c0_6'  : ( 'CNTVCTSS_EL0', 'Counter-timer Self-Synchronized Virtual Count register' ),
    'S3_3_c14_c2_0'  : ( 'CNTP_TVAL_EL0', 'Counter-timer Physical Timer TimerValue register' ),
    'S3_3_c14_c2_1'  : ( 'CNTP_CTL_EL0', 'Counter-timer Physical Timer Control register' ),
    'S3_3_c14_c2_2'  : ( 'CNTP_CVAL_EL0', 'Counter-timer Physical Timer CompareValue register' ),
    'S3_3_c14_c3_0'  : ( 'CNTV_TVAL_EL0', 'Counter-timer Virtual Timer TimerValue register' ),
    'S3_3_c14_c3_1'  : ( 'CNTV_CTL_EL0', 'Counter-timer Virtual Timer Control register' ),
    'S3_3_c14_c3_2'  : ( 'CNTV_CVAL_EL0', 'Counter-timer Virtual Timer CompareValue register' ),
    'S3_3_c14_c8_0'  : ( 'PMEVCNTR0_EL0', 'Performance Monitors Event Count Register 0' ),
    'S3_3_c14_c8_1'  : ( 'PMEVCNTR1_EL0', 'Performance Monitors Event Count Register 1' ),
    'S3_3_c14_c8_2'  : ( 'PMEVCNTR2_EL0', 'Performance Monitors Event Count Register 2' ),
    'S3_3_c14_c8_3'  : ( 'PMEVCNTR3_EL0', 'Performance Monitors Event Count Register 3' ),
    'S3_3_c14_c8_4'  : ( 'PMEVCNTR4_EL0', 'Performance Monitors Event Count Register 4' ),
    'S3_3_c14_c8_5'  : ( 'PMEVCNTR5_EL0', 'Performance Monitors Event Count Register 5' ),
    'S3_3_c14_c8_6'  : ( 'PMEVCNTR6_EL0', 'Performance Monitors Event Count Register 6' ),
    'S3_3_c14_c8_7'  : ( 'PMEVCNTR7_EL0', 'Performance Monitors Event Count Register 7' ),
    'S3_3_c14_c9_0'  : ( 'PMEVCNTR8_EL0', 'Performance Monitors Event Count Register 8' ),
    'S3_3_c14_c9_1'  : ( 'PMEVCNTR9_EL0', 'Performance Monitors Event Count Register 9' ),
    'S3_3_c14_c9_2'  : ( 'PMEVCNTR10_EL0', 'Performance Monitors Event Count Register 10' ),
    'S3_3_c14_c9_3'  : ( 'PMEVCNTR11_EL0', 'Performance Monitors Event Count Register 11' ),
    'S3_3_c14_c9_4'  : ( 'PMEVCNTR12_EL0', 'Performance Monitors Event Count Register 12' ),
    'S3_3_c14_c9_5'  : ( 'PMEVCNTR13_EL0', 'Performance Monitors Event Count Register 13' ),
    'S3_3_c14_c9_6'  : ( 'PMEVCNTR14_EL0', 'Performance Monitors Event Count Register 14' ),
    'S3_3_c14_c9_7'  : ( 'PMEVCNTR15_EL0', 'Performance Monitors Event Count Register 15' ),
    'S3_3_c14_c10_0' : ( 'PMEVCNTR16_EL0', 'Performance Monitors Event Count Register 16' ),
    'S3_3_c14_c10_1' : ( 'PMEVCNTR17_EL0', 'Performance Monitors Event Count Register 17' ),
    'S3_3_c14_c10_2' : ( 'PMEVCNTR18_EL0', 'Performance Monitors Event Count Register 18' ),
    'S3_3_c14_c10_3' : ( 'PMEVCNTR19_EL0', 'Performance Monitors Event Count Register 19' ),
    'S3_3_c14_c10_4' : ( 'PMEVCNTR20_EL0', 'Performance Monitors Event Count Register 20' ),
    'S3_3_c14_c10_5' : ( 'PMEVCNTR21_EL0', 'Performance Monitors Event Count Register 21' ),
    'S3_3_c14_c10_6' : ( 'PMEVCNTR22_EL0', 'Performance Monitors Event Count Register 22' ),
    'S3_3_c14_c10_7' : ( 'PMEVCNTR23_EL0', 'Performance Monitors Event Count Register 23' ),
    'S3_3_c14_c11_0' : ( 'PMEVCNTR24_EL0', 'Performance Monitors Event Count Register 24' ),
    'S3_3_c14_c11_1' : ( 'PMEVCNTR25_EL0', 'Performance Monitors Event Count Register 25' ),
    'S3_3_c14_c11_2' : ( 'PMEVCNTR26_EL0', 'Performance Monitors Event Count Register 26' ),
    'S3_3_c14_c11_3' : ( 'PMEVCNTR27_EL0', 'Performance Monitors Event Count Register 27' ),
    'S3_3_c14_c11_4' : ( 'PMEVCNTR28_EL0', 'Performance Monitors Event Count Register 28' ),
    'S3_3_c14_c11_5' : ( 'PMEVCNTR29_EL0', 'Performance Monitors Event Count Register 29' ),
    'S3_3_c14_c11_6' : ( 'PMEVCNTR30_EL0', 'Performance Monitors Event Count Register 30' ),
    'S3_3_c14_c11_7' : ( 'PMEVCNTR31_EL0', 'Performance Monitors Event Count Register 31' ),
    'S3_3_c14_c12_0' : ( 'PMEVTYPER0_EL0', 'Performance Monitors Event Type Register 0' ),
    'S3_3_c14_c12_1' : ( 'PMEVTYPER1_EL0', 'Performance Monitors Event Type Register 1' ),
    'S3_3_c14_c12_2' : ( 'PMEVTYPER2_EL0', 'Performance Monitors Event Type Register 2' ),
    'S3_3_c14_c12_3' : ( 'PMEVTYPER3_EL0', 'Performance Monitors Event Type Register 3' ),
    'S3_3_c14_c12_4' : ( 'PMEVTYPER4_EL0', 'Performance Monitors Event Type Register 4' ),
    'S3_3_c14_c12_5' : ( 'PMEVTYPER5_EL0', 'Performance Monitors Event Type Register 5' ),
    'S3_3_c14_c12_6' : ( 'PMEVTYPER6_EL0', 'Performance Monitors Event Type Register 6' ),
    'S3_3_c14_c12_7' : ( 'PMEVTYPER7_EL0', 'Performance Monitors Event Type Register 7' ),
    'S3_3_c14_c13_0' : ( 'PMEVTYPER8_EL0', 'Performance Monitors Event Type Register 8' ),
    'S3_3_c14_c13_1' : ( 'PMEVTYPER9_EL0', 'Performance Monitors Event Type Register 9' ),
    'S3_3_c14_c13_2' : ( 'PMEVTYPER10_EL0', 'Performance Monitors Event Type Register 10' ),
    'S3_3_c14_c13_3' : ( 'PMEVTYPER11_EL0', 'Performance Monitors Event Type Register 11' ),
    'S3_3_c14_c13_4' : ( 'PMEVTYPER12_EL0', 'Performance Monitors Event Type Register 12' ),
    'S3_3_c14_c13_5' : ( 'PMEVTYPER13_EL0', 'Performance Monitors Event Type Register 13' ),
    'S3_3_c14_c13_6' : ( 'PMEVTYPER14_EL0', 'Performance Monitors Event Type Register 14' ),
    'S3_3_c14_c13_7' : ( 'PMEVTYPER15_EL0', 'Performance Monitors Event Type Register 15' ),
    'S3_3_c14_c14_0' : ( 'PMEVTYPER16_EL0', 'Performance Monitors Event Type Register 16' ),
    'S3_3_c14_c14_1' : ( 'PMEVTYPER17_EL0', 'Performance Monitors Event Type Register 17' ),
    'S3_3_c14_c14_2' : ( 'PMEVTYPER18_EL0', 'Performance Monitors Event Type Register 18' ),
    'S3_3_c14_c14_3' : ( 'PMEVTYPER19_EL0', 'Performance Monitors Event Type Register 19' ),
    'S3_3_c14_c14_4' : ( 'PMEVTYPER20_EL0', 'Performance Monitors Event Type Register 20' ),
    'S3_3_c14_c14_5' : ( 'PMEVTYPER21_EL0', 'Performance Monitors Event Type Register 21' ),
    'S3_3_c14_c14_6' : ( 'PMEVTYPER22_EL0', 'Performance Monitors Event Type Register 22' ),
    'S3_3_c14_c14_7' : ( 'PMEVTYPER23_EL0', 'Performance Monitors Event Type Register 23' ),
    'S3_3_c14_c15_0' : ( 'PMEVTYPER24_EL0', 'Performance Monitors Event Type Register 24' ),
    'S3_3_c14_c15_1' : ( 'PMEVTYPER25_EL0', 'Performance Monitors Event Type Register 25' ),
    'S3_3_c14_c15_2' : ( 'PMEVTYPER26_EL0', 'Performance Monitors Event Type Register 26' ),
    'S3_3_c14_c15_3' : ( 'PMEVTYPER27_EL0', 'Performance Monitors Event Type Register 27' ),
    'S3_3_c14_c15_4' : ( 'PMEVTYPER28_EL0', 'Performance Monitors Event Type Register 28' ),
    'S3_3_c14_c15_5' : ( 'PMEVTYPER29_EL0', 'Performance Monitors Event Type Register 29' ),
    'S3_3_c14_c15_6' : ( 'PMEVTYPER30_EL0', 'Performance Monitors Event Type Register 30' ),
    'S3_3_c14_c15_7' : ( 'PMCCFILTR_EL0', 'Performance Monitors Cycle Count Filter Register' ),
    'S3_3_c14_c15_7' : ( 'PMEVTYPER31_EL0', 'Performance Monitors Event Type Register 31' ),
    'S3_4_c0_c0_0'   : ( 'VPIDR_EL2', 'Virtualization Processor ID Register' ),
    'S3_4_c0_c0_5'   : ( 'VMPIDR_EL2', 'Virtualization Multiprocessor ID Register' ),
    'S3_4_c1_c0_0'   : ( 'SCTLR_EL2', 'System Control Register (EL2)' ),
    'S3_4_c1_c0_1'   : ( 'ACTLR_EL2', 'Auxiliary Control Register (EL2)' ),
    'S3_4_c1_c1_0'   : ( 'HCR_EL2', 'Hypervisor Configuration Register' ),
    'S3_4_c1_c1_1'   : ( 'MDCR_EL2', 'Monitor Debug Configuration Register (EL2)' ),
    'S3_4_c1_c1_2'   : ( 'CPTR_EL2', 'Architectural Feature Trap Register (EL2)' ),
    'S3_4_c1_c1_3'   : ( 'HSTR_EL2', 'Hypervisor System Trap Register' ),
    'S3_4_c1_c1_4'   : ( 'HFGRTR_EL2', 'Hypervisor Fine-Grained Read Trap Register' ),
    'S3_4_c1_c1_5'   : ( 'HFGWTR_EL2', 'Hypervisor Fine-Grained Write Trap Register' ),
    'S3_4_c1_c1_6'   : ( 'HFGITR_EL2', 'Hypervisor Fine-Grained Instruction Trap Register' ),
    'S3_4_c1_c1_7'   : ( 'HACR_EL2', 'Hypervisor Auxiliary Control Register' ),
    'S3_4_c1_c2_0'   : ( 'ZCR_EL2', 'SVE Control Register for EL2' ),
    'S3_4_c1_c2_1'   : ( 'TRFCR_EL2', 'Trace Filter Control Register (EL2)' ),
    'S3_4_c1_c3_1'   : ( 'SDER32_EL2', 'AArch32 Secure Debug Enable Register' ),
    'S3_4_c2_c0_0'   : ( 'TTBR0_EL2', 'Translation Table Base Register 0 (EL2)' ),
    'S3_4_c2_c0_1'   : ( 'TTBR1_EL2', 'Translation Table Base Register 1 (EL2)' ),
    'S3_4_c2_c0_2'   : ( 'TCR_EL2', 'Translation Control Register (EL2)' ),
    'S3_4_c2_c1_0'   : ( 'VTTBR_EL2', 'Virtualization Translation Table Base Register' ),
    'S3_4_c2_c1_2'   : ( 'VTCR_EL2', 'Virtualization Translation Control Register' ),
    'S3_4_c2_c2_0'   : ( 'VNCR_EL2', 'Virtual Nested Control Register' ),
    'S3_4_c2_c6_0'   : ( 'VSTTBR_EL2', 'Virtualization Secure Translation Table Base Register' ),
    'S3_4_c2_c6_2'   : ( 'VSTCR_EL2', 'Virtualization Secure Translation Control Register' ),
    'S3_4_c3_c0_0'   : ( 'DACR32_EL2', 'Domain Access Control Register' ),
    'S3_4_c3_c1_4'   : ( 'HDFGRTR_EL2', 'Hypervisor Debug Fine-Grained Read Trap Register' ),
    'S3_4_c3_c1_5'   : ( 'HDFGWTR_EL2', 'Hypervisor Debug Fine-Grained Write Trap Register' ),
    'S3_4_c3_c1_6'   : ( 'HAFGRTR_EL2', 'Hypervisor Activity Monitors Fine-Grained Read Trap Register' ),
    'S3_4_c4_c0_0'   : ( 'SPSR_EL2', 'Saved Program Status Register (EL2)' ),
    'S3_4_c4_c0_1'   : ( 'ELR_EL2', 'Exception Link Register (EL2)' ),
    'S3_4_c4_c1_0'   : ( 'SP_EL1', 'Stack Pointer (EL1)' ),
    'S3_4_c4_c3_0'   : ( 'SPSR_irq', 'Saved Program Status Register (IRQ mode)' ),
    'S3_4_c4_c3_1'   : ( 'SPSR_abt', 'Saved Program Status Register (Abort mode)' ),
    'S3_4_c4_c3_2'   : ( 'SPSR_und', 'Saved Program Status Register (Undefined mode)' ),
    'S3_4_c4_c3_3'   : ( 'SPSR_fiq', 'Saved Program Status Register (FIQ mode)' ),
    'S3_4_c5_c0_1'   : ( 'IFSR32_EL2', 'Instruction Fault Status Register (EL2)' ),
    'S3_4_c5_c1_0'   : ( 'AFSR0_EL2', 'Auxiliary Fault Status Register 0 (EL2)' ),
    'S3_4_c5_c1_1'   : ( 'AFSR1_EL2', 'Auxiliary Fault Status Register 1 (EL2)' ),
    'S3_4_c5_c2_0'   : ( 'ESR_EL2', 'Exception Syndrome Register (EL2)' ),
    'S3_4_c5_c2_3'   : ( 'VSESR_EL2', 'Virtual SError Exception Syndrome Register' ),
    'S3_4_c5_c3_0'   : ( 'FPEXC32_EL2', 'Floating-Point Exception Control register' ),
    'S3_4_c5_c6_0'   : ( 'TFSR_EL2', 'Tag Fault Status Register (EL2)' ),
    'S3_4_c6_c0_0'   : ( 'FAR_EL2', 'Fault Address Register (EL2)' ),
    'S3_4_c6_c0_4'   : ( 'HPFAR_EL2', 'Hypervisor IPA Fault Address Register' ),
    'S3_4_c9_c9_0'   : ( 'PMSCR_EL2', 'Statistical Profiling Control Register (EL2)' ),
    'S3_4_c10_c2_0'  : ( 'MAIR_EL2', 'Memory Attribute Indirection Register (EL2)' ),
    'S3_4_c10_c3_0'  : ( 'AMAIR_EL2', 'Auxiliary Memory Attribute Indirection Register (EL2)' ),
    'S3_4_c10_c4_0'  : ( 'MPAMHCR_EL2', 'MPAM Hypervisor Control Register (EL2)' ),
    'S3_4_c10_c4_1'  : ( 'MPAMVPMV_EL2', 'MPAM Virtual Partition Mapping Valid Register' ),
    'S3_4_c10_c5_0'  : ( 'MPAM2_EL2', 'MPAM2 Register (EL2)' ),
    'S3_4_c10_c6_0'  : ( 'MPAMVPM0_EL2', 'MPAM Virtual PARTID Mapping Register 0' ),
    'S3_4_c10_c6_1'  : ( 'MPAMVPM1_EL2', 'MPAM Virtual PARTID Mapping Register 1' ),
    'S3_4_c10_c6_2'  : ( 'MPAMVPM2_EL2', 'MPAM Virtual PARTID Mapping Register 2' ),
    'S3_4_c10_c6_3'  : ( 'MPAMVPM3_EL2', 'MPAM Virtual PARTID Mapping Register 3' ),
    'S3_4_c10_c6_4'  : ( 'MPAMVPM4_EL2', 'MPAM Virtual PARTID Mapping Register 4' ),
    'S3_4_c10_c6_5'  : ( 'MPAMVPM5_EL2', 'MPAM Virtual PARTID Mapping Register 5' ),
    'S3_4_c10_c6_6'  : ( 'MPAMVPM6_EL2', 'MPAM Virtual PARTID Mapping Register 6' ),
    'S3_4_c10_c6_7'  : ( 'MPAMVPM7_EL2', 'MPAM Virtual PARTID Mapping Register 7' ),
    'S3_4_c12_c0_0'  : ( 'VBAR_EL2', 'Vector Base Address Register (EL2)' ),
    'S3_4_c12_c0_1'  : ( 'RVBAR_EL2', 'Reset Vector Base Address Register (if EL3 not implemented)' ),
    'S3_4_c12_c0_2'  : ( 'RMR_EL2', 'Reset Management Register (EL2)' ),
    'S3_4_c12_c1_1'  : ( 'VDISR_EL2', 'Virtual Deferred Interrupt Status Register' ),
    'S3_4_c12_c8_0'  : ( 'ICH_AP0R0_EL2', 'Interrupt Controller Hyp Active Priorities Group 0 Register 0' ),
    'S3_4_c12_c8_1'  : ( 'ICH_AP0R1_EL2', 'Interrupt Controller Hyp Active Priorities Group 0 Register 1' ),
    'S3_4_c12_c8_2'  : ( 'ICH_AP0R2_EL2', 'Interrupt Controller Hyp Active Priorities Group 0 Register 2' ),
    'S3_4_c12_c8_3'  : ( 'ICH_AP0R3_EL2', 'Interrupt Controller Hyp Active Priorities Group 0 Register 3' ),
    'S3_4_c12_c9_0'  : ( 'ICH_AP1R0_EL2', 'Interrupt Controller Hyp Active Priorities Group 1 Register 0' ),
    'S3_4_c12_c9_1'  : ( 'ICH_AP1R1_EL2', 'Interrupt Controller Hyp Active Priorities Group 1 Register 1' ),
    'S3_4_c12_c9_2'  : ( 'ICH_AP1R2_EL2', 'Interrupt Controller Hyp Active Priorities Group 1 Register 2' ),
    'S3_4_c12_c9_3'  : ( 'ICH_AP1R3_EL2', 'Interrupt Controller Hyp Active Priorities Group 1 Register 3' ),
    'S3_4_c12_c9_5'  : ( 'ICC_SRE_EL2', 'Interrupt Controller System Register Enable register (EL2)' ),
    'S3_4_c12_c11_0' : ( 'ICH_HCR_EL2', 'Interrupt Controller Hyp Control Register' ),
    'S3_4_c12_c11_1' : ( 'ICH_VTR_EL2', 'Interrupt Controller VGIC Type Register' ),
    'S3_4_c12_c11_2' : ( 'ICH_MISR_EL2', 'Interrupt Controller Maintenance Interrupt State Register' ),
    'S3_4_c12_c11_3' : ( 'ICH_EISR_EL2', 'Interrupt Controller End of Interrupt Status Register' ),
    'S3_4_c12_c11_5' : ( 'ICH_ELRSR_EL2', 'Interrupt Controller Empty List Register Status Register' ),
    'S3_4_c12_c11_7' : ( 'ICH_VMCR_EL2', 'Interrupt Controller Virtual Machine Control Register' ),
    'S3_4_c12_c12_0' : ( 'ICH_LR0_EL2', 'Interrupt Controller List Register 0' ),
    'S3_4_c12_c12_1' : ( 'ICH_LR1_EL2', 'Interrupt Controller List Register 1' ),
    'S3_4_c12_c12_2' : ( 'ICH_LR2_EL2', 'Interrupt Controller List Register 2' ),
    'S3_4_c12_c12_3' : ( 'ICH_LR3_EL2', 'Interrupt Controller List Register 3' ),
    'S3_4_c12_c12_4' : ( 'ICH_LR4_EL2', 'Interrupt Controller List Register 4' ),
    'S3_4_c12_c12_5' : ( 'ICH_LR5_EL2', 'Interrupt Controller List Register 5' ),
    'S3_4_c12_c12_6' : ( 'ICH_LR6_EL2', 'Interrupt Controller List Register 6' ),
    'S3_4_c12_c12_7' : ( 'ICH_LR7_EL2', 'Interrupt Controller List Register 7' ),
    'S3_4_c12_c13_0' : ( 'ICH_LR8_EL2', 'Interrupt Controller List Register 8' ),
    'S3_4_c12_c13_1' : ( 'ICH_LR9_EL2', 'Interrupt Controller List Register 9' ),
    'S3_4_c12_c13_2' : ( 'ICH_LR10_EL2', 'Interrupt Controller List Register 10' ),
    'S3_4_c12_c13_3' : ( 'ICH_LR11_EL2', 'Interrupt Controller List Register 11' ),
    'S3_4_c12_c13_4' : ( 'ICH_LR12_EL2', 'Interrupt Controller List Register 12' ),
    'S3_4_c12_c13_5' : ( 'ICH_LR13_EL2', 'Interrupt Controller List Register 13' ),
    'S3_4_c12_c13_6' : ( 'ICH_LR14_EL2', 'Interrupt Controller List Register 14' ),
    'S3_4_c12_c13_7' : ( 'ICH_LR15_EL2', 'Interrupt Controller List Register 15' ),
    'S3_4_c13_c0_1'  : ( 'CONTEXTIDR_EL2', 'Context ID Register (EL2)' ),
    'S3_4_c13_c0_2'  : ( 'TPIDR_EL2', 'EL2 Software Thread ID Register' ),
    'S3_4_c13_c0_7'  : ( 'SCXTNUM_EL2', 'EL2 Read/Write Software Context Number' ),
    'S3_4_c13_c8_0'  : ( 'AMEVCNTVOFF00_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 0' ),
    'S3_4_c13_c8_1'  : ( 'AMEVCNTVOFF01_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 1' ),
    'S3_4_c13_c8_2'  : ( 'AMEVCNTVOFF02_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 2' ),
    'S3_4_c13_c8_3'  : ( 'AMEVCNTVOFF03_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 3' ),
    'S3_4_c13_c8_4'  : ( 'AMEVCNTVOFF04_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 4' ),
    'S3_4_c13_c8_5'  : ( 'AMEVCNTVOFF05_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 5' ),
    'S3_4_c13_c8_6'  : ( 'AMEVCNTVOFF06_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 6' ),
    'S3_4_c13_c8_7'  : ( 'AMEVCNTVOFF07_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 7' ),
    'S3_4_c13_c9_0'  : ( 'AMEVCNTVOFF08_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 8' ),
    'S3_4_c13_c9_1'  : ( 'AMEVCNTVOFF09_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 9' ),
    'S3_4_c13_c9_2'  : ( 'AMEVCNTVOFF010_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 10' ),
    'S3_4_c13_c9_3'  : ( 'AMEVCNTVOFF011_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 11' ),
    'S3_4_c13_c9_4'  : ( 'AMEVCNTVOFF012_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 12' ),
    'S3_4_c13_c9_5'  : ( 'AMEVCNTVOFF013_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 13' ),
    'S3_4_c13_c9_6'  : ( 'AMEVCNTVOFF014_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 14' ),
    'S3_4_c13_c9_7'  : ( 'AMEVCNTVOFF015_EL2', 'Activity Monitors Event Counter Virtual Offset Register 0 15' ),
    'S3_4_c13_c10_0' : ( 'AMEVCNTVOFF10_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 0' ),
    'S3_4_c13_c10_1' : ( 'AMEVCNTVOFF11_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 1' ),
    'S3_4_c13_c10_2' : ( 'AMEVCNTVOFF12_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 2' ),
    'S3_4_c13_c10_3' : ( 'AMEVCNTVOFF13_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 3' ),
    'S3_4_c13_c10_4' : ( 'AMEVCNTVOFF14_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 4' ),
    'S3_4_c13_c10_5' : ( 'AMEVCNTVOFF15_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 5' ),
    'S3_4_c13_c10_6' : ( 'AMEVCNTVOFF16_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 6' ),
    'S3_4_c13_c10_7' : ( 'AMEVCNTVOFF17_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 7' ),
    'S3_4_c13_c11_0' : ( 'AMEVCNTVOFF18_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 8' ),
    'S3_4_c13_c11_1' : ( 'AMEVCNTVOFF19_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 9' ),
    'S3_4_c13_c11_2' : ( 'AMEVCNTVOFF110_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 10' ),
    'S3_4_c13_c11_3' : ( 'AMEVCNTVOFF111_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 11' ),
    'S3_4_c13_c11_4' : ( 'AMEVCNTVOFF112_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 12' ),
    'S3_4_c13_c11_5' : ( 'AMEVCNTVOFF113_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 13' ),
    'S3_4_c13_c11_6' : ( 'AMEVCNTVOFF114_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 14' ),
    'S3_4_c13_c11_7' : ( 'AMEVCNTVOFF115_EL2', 'Activity Monitors Event Counter Virtual Offset Register 1 15' ),
    'S3_4_c14_c0_3'  : ( 'CNTVOFF_EL2', 'Counter-timer Virtual Offset register' ),
    'S3_4_c14_c0_6'  : ( 'CNTPOFF_EL2', 'Counter-timer Physical Offset register' ),
    'S3_4_c14_c1_0'  : ( 'CNTHCTL_EL2', 'Counter-timer Hypervisor Control register' ),
    'S3_4_c14_c2_0'  : ( 'CNTHP_TVAL_EL2', 'Counter-timer Physical Timer TimerValue register (EL2)' ),
    'S3_4_c14_c2_1'  : ( 'CNTHP_CTL_EL2', 'Counter-timer Hypervisor Physical Timer Control register' ),
    'S3_4_c14_c2_2'  : ( 'CNTHP_CVAL_EL2', 'Counter-timer Physical Timer CompareValue register (EL2)' ),
    'S3_4_c14_c3_0'  : ( 'CNTHV_TVAL_EL2', 'Counter-timer Virtual Timer TimerValue Register (EL2)' ),
    'S3_4_c14_c3_1'  : ( 'CNTHV_CTL_EL2', 'Counter-timer Virtual Timer Control register (EL2)' ),
    'S3_4_c14_c3_2'  : ( 'CNTHV_CVAL_EL2', 'Counter-timer Virtual Timer CompareValue register (EL2)' ),
    'S3_4_c14_c4_0'  : ( 'CNTHVS_TVAL_EL2', 'Counter-timer Secure Virtual Timer TimerValue register (EL2)' ),
    'S3_4_c14_c4_1'  : ( 'CNTHVS_CTL_EL2', 'Counter-timer Secure Virtual Timer Control register (EL2)' ),
    'S3_4_c14_c4_2'  : ( 'CNTHVS_CVAL_EL2', 'Counter-timer Secure Virtual Timer CompareValue register (EL2)' ),
    'S3_4_c14_c5_0'  : ( 'CNTHPS_TVAL_EL2', 'Counter-timer Secure Physical Timer TimerValue register (EL2)' ),
    'S3_4_c14_c5_1'  : ( 'CNTHPS_CTL_EL2', 'Counter-timer Secure Physical Timer Control register (EL2)' ),
    'S3_4_c14_c5_2'  : ( 'CNTHPS_CVAL_EL2', 'Counter-timer Secure Physical Timer CompareValue register (EL2)' ),
    'S3_6_c1_c0_0'   : ( 'SCTLR_EL3', 'System Control Register (EL3)' ),
    'S3_6_c1_c0_1'   : ( 'ACTLR_EL3', 'Auxiliary Control Register (EL3)' ),
    'S3_6_c1_c1_0'   : ( 'SCR_EL3', 'Secure Configuration Register' ),
    'S3_6_c1_c1_1'   : ( 'SDER32_EL3', 'AArch32 Secure Debug Enable Register' ),
    'S3_6_c1_c1_2'   : ( 'CPTR_EL3', 'Architectural Feature Trap Register (EL3)' ),
    'S3_6_c1_c2_0'   : ( 'ZCR_EL3', 'SVE Control Register for EL3' ),
    'S3_6_c1_c3_1'   : ( 'MDCR_EL3', 'Monitor Debug Configuration Register (EL3)' ),
    'S3_6_c2_c0_0'   : ( 'TTBR0_EL3', 'Translation Table Base Register 0 (EL3)' ),
    'S3_6_c2_c0_2'   : ( 'TCR_EL3', 'Translation Control Register (EL3)' ),
    'S3_6_c4_c0_0'   : ( 'SPSR_EL3', 'Saved Program Status Register (EL3)' ),
    'S3_6_c4_c0_1'   : ( 'ELR_EL3', 'Exception Link Register (EL3)' ),
    'S3_6_c4_c1_0'   : ( 'SP_EL2', 'Stack Pointer (EL2)' ),
    'S3_6_c5_c1_0'   : ( 'AFSR0_EL3', 'Auxiliary Fault Status Register 0 (EL3)' ),
    'S3_6_c5_c1_1'   : ( 'AFSR1_EL3', 'Auxiliary Fault Status Register 1 (EL3)' ),
    'S3_6_c5_c2_0'   : ( 'ESR_EL3', 'Exception Syndrome Register (EL3)' ),
    'S3_6_c5_c6_0'   : ( 'TFSR_EL3', 'Tag Fault Status Register (EL3)' ),
    'S3_6_c6_c0_0'   : ( 'FAR_EL3', 'Fault Address Register (EL3)' ),
    'S3_6_c10_c2_0'  : ( 'MAIR_EL3', 'Memory Attribute Indirection Register (EL3)' ),
    'S3_6_c10_c3_0'  : ( 'AMAIR_EL3', 'Auxiliary Memory Attribute Indirection Register (EL3)' ),
    'S3_6_c10_c5_0'  : ( 'MPAM3_EL3', 'MPAM3 Register (EL3)' ),
    'S3_6_c12_c0_0'  : ( 'VBAR_EL3', 'Vector Base Address Register (EL3)' ),
    'S3_6_c12_c0_1'  : ( 'RVBAR_EL3', 'Reset Vector Base Address Register (if EL3 implemented)' ),
    'S3_6_c12_c0_2'  : ( 'RMR_EL3', 'Reset Management Register (EL3)' ),
    'S3_6_c12_c12_4' : ( 'ICC_CTLR_EL3', 'Interrupt Controller Control Register (EL3)' ),
    'S3_6_c12_c12_5' : ( 'ICC_SRE_EL3', 'Interrupt Controller System Register Enable register (EL3)' ),
    'S3_6_c12_c12_7' : ( 'ICC_IGRPEN1_EL3', 'Interrupt Controller Interrupt Group 1 Enable register (EL3)' ),
    'S3_6_c13_c0_2'  : ( 'TPIDR_EL3', 'EL3 Software Thread ID Register' ),
    'S3_6_c13_c0_7'  : ( 'SCXTNUM_EL3', 'EL3 Read/Write Software Context Number' ),
    'S3_7_c14_c2_0'  : ( 'CNTPS_TVAL_EL1', 'Counter-timer Physical Secure Timer TimerValue register' ),
    'S3_7_c14_c2_1'  : ( 'CNTPS_CTL_EL1', 'Counter-timer Physical Secure Timer Control register' ),
    'S3_7_c14_c2_2'  : ( 'CNTPS_CVAL_EL1', 'Counter-timer Physical Secure Timer CompareValue register' ),
}

def pstate_insn(op2):
    pstate = PSTATE_ACCESS.get(op2)
    if pstate:
        return pstate
    else:
        return ('#{:b}'.format(op2))

def sysreg_insn(op0, op1, CRn, CRm, op2):
    sr = 'S{}_{}_c{}_c{}_{}'.format(op0, op1, CRn, CRm, op2)
    #print(sr)
    reg = SYSTEM_REGISTERS.get(sr)
    if reg:
        name = reg[0]
        description = reg[1]
        return name
    else:
        return sr

def process_msr(insn):
    assert(insn & 0xFFC00000 == 0xD5000000)
    L   = (insn >> 21) &  0x1
    op0 = (insn >> 19) &  0x3
    op1 = (insn >> 16) &  0x7
    CRn = (insn >> 12) &  0xf
    CRm = (insn >>  8) &  0xf
    op2 = (insn >>  5) &  0x7
    Rt  = (insn >>  0) & 0x1f
    if L == 0b0 and op0 == 0b00 and CRn == 0b0100 and Rt == 0b11111:
        return pstate_insn(op2)
    elif op0 != 0b00:
        return sysreg_insn(op0, op1, CRn, CRm, op2)
    else:
        return '{:08x}'.format(insn)


class Aarch64SysRegHook(idaapi.IDP_Hooks):
    CUSTOM_INSTRUCTIONS = {idaapi.ARM_mrs, idaapi.ARM_msr, idaapi.ARM_sys}
    INDENT = 16

    def ev_out_operand(self, outctx, op):
        if outctx.insn.itype in self.CUSTOM_INSTRUCTIONS:
            insn = outctx.insn
            #print("ev_out_operand")
            if op.n < 2:
                if outctx.insn.itype == idaapi.ARM_mrs:
                    if op.n == 1:
                        outctx.out_colored_register_line(sysreg_insn(3, insn.ops[1].value, insn.ops[2].reg, insn.ops[3].reg, insn.ops[4].value))
                    else:
                        return 0
                elif outctx.insn.itype == idaapi.ARM_msr:
                    if op.n == 0:
                        outctx.out_colored_register_line(process_msr(idaapi.get_dword(outctx.insn.ea)))
                    else:
                        if insn.ops[2].type == idaapi.o_void:
                            return 0
                        else:
                            insn.itype = ITYPE_MSR
                            outctx.out_one_operand(4)
                            insn.itype = idaapi.ARM_msr
                elif outctx.insn.itype == idaapi.ARM_sys:
                    if op.n == 0:
                        outctx.out_colored_register_line(sysreg_insn(1, insn.ops[0].value, insn.ops[1].reg, insn.ops[2].reg, insn.ops[3].value).split()[1].strip())
                    else:
                        insn.itype = ITYPE_SYS
                        outctx.out_one_operand(4)
                        insn.itype = idaapi.ARM_sys
                '''
                for i in range(0, 6):
                    op = outctx.insn.ops[i]
                    print(F'Op{op.n}: type: {op.type} reg: {op.reg} value: {op.value}\n')
                '''
            return 1
        return 0
    def ev_out_mnem(self, outctx):
        if outctx.insn.itype == idaapi.ARM_sys:
            #print("ev_out_insn")
            insn = outctx.insn
            outctx.out_custom_mnem(sysreg_insn(1, insn.ops[0].value, insn.ops[1].reg, insn.ops[2].reg, insn.ops[3].value).split()[0].strip(), self.INDENT)
            return 1
        return 0

class Aarch64SysRegPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
    comment = "Aarch64 Apple SysReg extension"
    wanted_hotkey = ""
    help = "Runs transparently"
    wanted_name = "Aarch64 SysReg"
    hook = None
    enabled = 1

    def init(self):
        if idaapi.ph_get_id() != idaapi.PLFM_ARM or idaapi.BADADDR <= 0xFFFFFFFF:
            return idaapi.PLUGIN_SKIP
        if not ida_hexrays.init_hexrays_plugin():
            print(("[-] {0} : no decompiler available, skipping".format(self.wanted_name)))
            return idaapi.PLUGIN_SKIP
        print("%s init"%self.comment)

        try:
            with open(APPLE_REGS_CFGFILE, "r") as fd:
                d = json.load(fd)
        except:
            d = []
            print(("[-] {0} : Loading apple_regs.json failed, skipping".format(self.wanted_name)))

        for r in d:
            sr = 'S{}_{}_c{}_c{}_{}'.format(*r['enc'])
            SYSTEM_REGISTERS[sr] = (r['name'], r['fullname'])
        self.hook = Aarch64SysRegHook()
        self.hook.hook()
        return idaapi.PLUGIN_KEEP

    def run():
        pass

    def term(self):
        if self.hook is not None:
            self.hook.unhook()
        print("%s unloaded"%self.comment)

def PLUGIN_ENTRY():
    return Aarch64SysRegPlugin()


repository: matthewrhannon/IDAPython-Automation-Scripts
file: cleanup_my_fuck_up.py
created_at: 2024-09-24 12:53:57+00:00
pushed_at: 2024-09-24 13:32:30+00:00
description:
Various scripts written in IDAPython to perform various tasks to augment reverse engineering using IDA Pro. Most of the scripts perform some form of automation for signatures, deobfuscation, cleaning sections such as .pdata and RUNTIME_FUNCTION SEH exception handling, LLM Auto-Reversing Application (functions, variables, structures, etc..)

url:
https://github.com/matthewrhannon/IDAPython-Automation-Scripts/blob/cad9d3a5bb5a92ac65736b674a7166348a7ebcc6/cleanup_my_fuck_up.py

content:
#!/usr/bin/env python3

import idautils
import idaapi
import idc
import ida_bytes
import ida_funcs
import ida_kernwin
import nltk
from nltk.corpus import words
import string
import ida_nalt

import sys

def main():
    #if len(sys.argv) != 2:
    #    print("Usage: python script.py input.txt")
    #    sys.exit(1)
    
    filename = "C:\\Users\\Unknown\\Documents\\addys.txt" #sys.argv[1]

    try:
        with open(filename, 'r') as file:
            for line_num, line in enumerate(file, 1):
                # Remove leading/trailing whitespace
                line = line.strip()
                
                # Skip empty lines
                if not line:
                    continue
                
                # Remove '0x' or '0X' prefix if present
                if line.lower().startswith('0x'):
                    hex_str = line[2:]
                else:
                    hex_str = line
                
                # Remove any spaces or tabs within the hex string
                hex_str = ''.join(hex_str.split())
                
                try:
                    # Convert hex string to integer
                    int_value = int(hex_str, 16)
                    
                    # Call the process_me function with the integer value
                    process_bad_function(int_value)
                except ValueError:
                    print(f"Invalid hex value on line {line_num}: {line}")
    except FileNotFoundError:
        print(f"File not found: {filename}")


def is_valid_string(ea, string_type):
    """
    Checks if the given address contains a valid string of the specified type.
    """
    length = ida_bytes.get_max_strlit_length(ea, string_type)
    return length > 0

# Ensure the necessary resources are downloaded
nltk.download('words')

# Load the word list
english_words = set(words.words())

# Function to check if a string is a valid word
def is_valid_word(s):
    
    #Make sure it is a string type
    res = issubclass(type(s), str)
    if res == False:
        return False
        
    print('DEBUG: s=' + s)
    
    english_word_counter = 0
    parts = s
    
    for part in parts.split():
       if  part.lower() in english_words:
           english_word_counter = english_word_counter + 1
           
    print('DEBUG: english_word_counter=0x' + hex(english_word_counter))
    
    if english_word_counter >= 1:
        return True
    else:
        return False

# Function to extract a potential string from memory and check if it is a valid word
#def check_memory_for_string(ea, length=100: Note: change made 9/19/2024 @ 6:40 pm
#def check_memory_for_string(ea, length=512):
def check_memory_for_string(ea, length=256):
    encodings = ['utf-8', 'ascii', 'utf-16', 'utf-32']
    str_types = [idc.STRTYPE_C, idc.STRTYPE_UNICODE]
    
    try:
        for str_type in str_types:
            extracted_str = idc.get_strlit_contents(ea, length, str_type)
            if not extracted_str:
                continue

            for encoding in encodings:
                try:
                    decoded_str = extracted_str.decode(encoding).strip(string.punctuation)
                    # Check if the decoded string is a valid word
                    if is_valid_word(decoded_str):
                        print(f"Address {hex(ea)} contains a valid {encoding} word: '{decoded_str}'")
                        return True
                except (UnicodeDecodeError, AttributeError):
                    continue  # If decoding fails or if there's no valid string, continue with the next encoding
    except Exception as e:
        print(f"Error while checking memory at {hex(ea)}: {e}")
    
    return False

def process_rdata_segment():
    """Process the .rdata segment to remove any instructions and define valid data."""
    rdata_seg = None
    for seg_ea in idautils.Segments():
        seg = idaapi.getseg(seg_ea)
        if seg and idc.get_segm_name(seg.start_ea) == ".rdata":
            rdata_seg = seg
            break
    
    if not rdata_seg:
        print("No .rdata segment found.")
        return
    
    start_ea = rdata_seg.start_ea
    end_ea = rdata_seg.end_ea
    
    undefine_instructions_in_rdata(start_ea, end_ea)
    define_strings_in_rdata(start_ea, end_ea)

def undefine_instructions_in_rdata(start_ea, end_ea):
    """Undefine any instructions in the .rdata segment and try to identify valid data."""
    print(f"Undefining instructions in .rdata from 0x{start_ea:X} to 0x{end_ea:X}.")
    ea = start_ea
    while ea < end_ea:
        flags = ida_bytes.get_full_flags(ea)
        if ida_bytes.is_code(flags):
            print(f"Undefining instruction at 0x{ea:X}.")
            idc.del_items(ea, idc.DELIT_SIMPLE)
        ea = idc.next_head(ea)
        
def undefine_area(start_ea, end_ea):
    """Undefine all instructions and data in the specified range."""
    print(f"Undefining area from 0x{start_ea:X} to 0x{end_ea:X}.")
    ea = start_ea
    while ea < end_ea:
        flags = idc.get_full_flags(ea)
        if ida_bytes.is_code(flags) or ida_bytes.is_data(flags):
            print(f"Undefining item at 0x{ea:X}.")
            idc.del_items(ea, idc.DELIT_SIMPLE)
        ea = idc.next_head(ea)
        
def undefine_function(ea):
    """Undefine the function and its bytes if the function is invalid."""
    func = ida_funcs.get_func(ea)
    if func:
        print(f"Deleting function at 0x{ea:X}.")
        ida_funcs.del_func(ea)
       #undefine_area(func.start_ea, func.end_ea)
    else:
        print(f"No function found at 0x{ea:X} to delete.")

def is_within_segment(ea):
    """Check if the given address is within a valid segment."""
    seg = idaapi.getseg(ea)
    if seg:
        return seg.start_ea <= ea < seg.end_ea
    return False

def aggressively_undefine_surrounding_area(ea, range_size=0x10):
    """Undefine the area around the current address, looking upwards and downwards."""
    temp = here()
    pos = idc.goto(ea)
    segshit = idc.here()
    idc.goto(temp)
    
    #seg = idaapi.getseg(ea)
    seg = idaapi.getseg(idaapi.to_ea(segshit))
    if not seg:
        print(f"Error: Unable to find segment for address 0x{ea:X}.")
        return
    
    start_ea = max(ea - range_size, seg.start_ea)
    end_ea = min(ea + range_size, seg.end_ea)
    print(f"Aggressively undefining surrounding area from 0x{start_ea:X} to 0x{end_ea:X}.")
    undefine_area(start_ea, end_ea)

def create_valid_function(rva, end_ea=None):
    """Create a valid function at the given RVA, correcting boundaries if necessary."""
    if end_ea is None:
        # Default to scanning for the end of the function
        end_ea = idaapi.BADADDR
        for ref in idautils.XrefsFrom(rva):
            if ref.type == idc.fl_F and is_within_segment(ref.to):
                end_ea = ref.to
                break
    
    # If end_ea is still BADADDR, assume a default function size
    if end_ea == idaapi.BADADDR or not is_within_segment(end_ea):
        end_ea = rva + 0x10 #0x10  # Adjust this size as needed

    print(f"Attempting to create function at 0x{rva:X} with end at 0x{end_ea:X}.")
    
    # Undefine any existing code or data in the range
    undefine_area(rva, end_ea)
    
    ea = rva
    while ea < end_ea:
        if ida_kernwin.user_cancelled():
            print("Script cancelled by user.")
            return False
        if not ida_bytes.is_code(ida_bytes.get_full_flags(ea)):
            if not idc.create_insn(ea):
                print(f"Failed to define instruction at 0x{ea:X}. Undefining surrounding area and retrying.")
                aggressively_undefine_surrounding_area(ea)
                if not idc.create_insn(ea):
                    print(f"Failed to define instruction again at 0x{ea:X}. Aborting.")
                    return False
        ea = idc.next_head(ea)
    
    if ida_funcs.add_func(rva, end_ea):
        print(f"Successfully created a valid function at 0x{rva:X} - 0x{end_ea:X}")
    else:
        print(f"Failed to create function at 0x{rva:X}.")

def process_function_at_rva(rva):
    """Check if a function exists at the given RVA and ensure it is valid based on alignment and boundaries."""
    if not is_within_segment(rva):
        print(f"Address 0x{rva:X} is not within a valid segment. Skipping.")
        return False
    
    func = ida_funcs.get_func(rva)
    
    # Ensure the function starts exactly at the given RVA
    if func and func.start_ea == rva:
        return True
    elif func:
        print(f"Function exists but starts at 0x{func.start_ea:X}, expected 0x{rva:X}. Undefining and correcting.")
        undefine_function(func.start_ea)
        create_valid_function(rva)
        return False
    else:
        print(f"No function found at 0x{rva:X}. Creating new function.")
        create_valid_function(rva)
        return True
        
def process_bad_function(rva):
    try:
        # Ensure it's a valid hexadecimal number
        #rva = int(hex_str, 16)
        undefine_function(rva)
        if rva is not None:
            #if not process_function_at_rva(rva):
            #    print(f"Invalid function at 0x{rva:X}. Cleaning up and redefining.")
            create_valid_function(rva)
            #else:
            #    print(f"Valid function processed at 0x{rva:X}.")
        else:
            print(f"Skipping line due to invalid RVA computation.")
    except:
        print('!!! EXCEPTION CAUGHT !!!')
        
def PLUGIN_ENTRY():
    print('Starting fuck upd fixer script')
    
    main()
    
    print('Ending fuck upd fixer script')


repository: quarkslab/qsynthesis
file: ida_plugin/qsynthesis_plugin.py
description: Greybox Synthesizer geared for deobfuscation of assembly instructions.
wanted_name: QSynthesis
comment: QSynthesis IDA plugin
created_at: 2021-07-30 07:42:13+00:00
pushed_at: 2025-02-16 13:24:15+00:00
url:
https://github.com/quarkslab/qsynthesis/blob/44e7318200f8ce1dd0fd79a849d2c68e9a3694bc/ida_plugin/qsynthesis_plugin.py

content:
#!/usr/bin/env python3

from qsynthesis.plugin.dependencies import ida_idaapi, ida_kernwin, QTRACEIDA_ENABLED, TRITON_ENABLED
import qsynthesis


class QSynthesisPlugin(ida_idaapi.plugin_t):
    """
    Main QSynthesis plugin object. Depending on the presence of Qtrace-IDA it
    behaves differently.
    """

    flags = ida_idaapi.PLUGIN_UNL
    comment = "QSynthesis IDA plugin"
    help = "Plugin to perform program synthesis of symbolic expressions"
    wanted_name = "QSynthesis"
    wanted_hotkey = "Alt-S"

    def init(self) -> int:
        """
        Initialize the plugin upon loading. Register it as an IDA addon so
        that Qtrace-IDA will be able to find it through its loading mechanism.
        If triton is not found disable it permanently.
        """
        addon_info = ida_kernwin.addon_info_t()
        addon_info.id = "com.quarkslab.qtraceida.qsynthesis.plugin"
        addon_info.name = self.wanted_name
        addon_info.producer = "Quarkslab"
        addon_info.version = qsynthesis.__version__
        addon_info.url = "https://github.com/quarkslab/qsynthesis"
        addon_info.freeform = "Copyright (c) 2020 - All Rights Reserved"
        ida_kernwin.register_addon(addon_info)
        self.view = None
        return ida_idaapi.PLUGIN_OK if TRITON_ENABLED else ida_idaapi.PLUGIN_SKIP

    def run(self, arg) -> None:
        """
        Run the plugin. If Qtrace-IDA is enabled, the action_handler_t
        should have been registered so call it. If no Qtrace-IDA is
        present open the main view as a standalone plugin.
        """
        print("Running QSynthesis")

        if QTRACEIDA_ENABLED and 'qtraceida' in globals():  # qtaceida has been loaded launched it through Qtrace-IDA mechanism
            import qtraceida
            qtrace = qtraceida.get_qtrace()  # Open Qtrace if it was not already done
            # If QtraceIDA enable the action should have been registered
            from qsynthesis.plugin.actions import SynthetizerViewHook
            ida_kernwin.process_ui_action(SynthetizerViewHook.view_id)
            if not qtrace.trace_opened():
                print("Please open a trace before using QSynthesis")
        else:  # Qtrace-IDA not loaded or not installed open QSynthesis on its own
            from qsynthesis.plugin.view import SynthesizerView
            self.view = SynthesizerView(None)
            self.view.Show()  # Show will call OnCreate that will call init
            self.view.open_no_trace()

    def term(self) -> None:
        pass


def PLUGIN_ENTRY():
    qsynthesis.qsynthesis_plugin = QSynthesisPlugin()
    return qsynthesis.qsynthesis_plugin


def main():
    # Standalone IDA-less mode. This way of launching QSynthesis
    # is mostly here for testing (faster than with IDA)
    import sys
    from PyQt5.QtWidgets import QApplication
    from qsynthesis.plugin.view import SynthesizerView
    app = QApplication(sys.argv)
    widget = SynthesizerView(None)
    widget.init()
    widget.show()
    app.exec_()


if __name__ == "__main__":
    main()


repository: csl-ugent/TREX
file: containers/ida/plugins/ida_plugin.py
description: Toolbox for Reverse Engineering
created_at: 2024-08-20 14:39:11+00:00
pushed_at: 2025-02-07 15:39:21+00:00
url:
https://github.com/csl-ugent/TREX/blob/07604b35c559c53bed61c60c7bd574fcad350364/containers/ida/plugins/ida_plugin.py

content:
import getopt
import os
import re
import sys
import json

from Tool import EDGE_FALLTHROUGH, EDGE_JUMP
from idapython import InsAssembled, InsString, InsIsHanging, InsIsData, InsIsUnknown, iter_blocks, BblOutgoingInsns, \
    InsNext, InsIsFlow, FunctionAppendChunk, BblIncomingInsns, InsDefinesPC, InsIsBranch, InsIsCall, iter_all_insns, \
    InsSwitchInfo, SwitchNrCases, Dechunkize, iter_insns, iter_blocks_in_function, iter_hanging, InsIsLastInBbl, \
    InsNotInTextSection, InsIsConditional, args, find_section, analysed_binary

from idaapi import get_func
from idautils import DecodeInstruction, Functions, Strings, DataRefsTo
from idc import prev_head, next_head, print_operand, print_insn_mnem, get_func_name, auto_wait, qexit

# ~/idapro-7.5/ida64 -a- -A -Lida.log -S$PWD/containers/ida/plugins/ida_plugin.py <binary path>
# ./idaq -c -A -S"/home/jens/repositories/ida-plugins/test.py [<argument>]*" <binary path and name>
#
# Generate a list of instructions, including "hanging instructions"

INVALID = -1

# debug
debug = True
stop = False

# output files
ifile = None
ffile = None
bfile = None
cfile = None
efile = None
stringfile = None
stringxreffile = None

# global statistics
nr_hanging = 0
function_id = 0
block_id = 0
edge_id = 0

# global variables
plt_start = 0
plt_end = 0
color_codes_re = re.compile(r'\x1b[^m]*m')

# arguments
assoc_hanging_actions = ''
assoc_hanging_jump = False
switch_dest_to_tail = False
suffix = ''


def AddressInPLT(address):
    return plt_start <= address < plt_end


def AddressInRange(S, E, address):
    return S <= address < E


edge_types = ['FALLTHROUGH', 'JUMP', 'CALL', 'CALL_FALLTHROUGH']


def print_edge(src, branch, dst, etype, decoded):
    global edge_id

    if etype == EDGE_FALLTHROUGH:
        delta = dst - branch
        if delta > 4:
            # CHANGED assert decoded.is_macro(), "0x%x (0x%x) -> 0x%x (type %d) expected delta of 8, got %d" % (src, branch, dst, etype, delta)
            # here, IDA has probably merged two instructions, for example MOVW/MOVT
            branch += decoded.size - 4
    etype = edge_types[etype]
    if not AddressInPLT(dst):
        efile.write("%d,%d,%d,%d,%d,%s\n" % (edge_id, src, branch, dst, 0, etype))

    edge_id += 1


def dangerous_flow_instruction(i):
    result = False

    asm = InsAssembled(i)

    # Certain instruction cause IDA to infinitely loop in its analysis phase.
    # This is the case in cactusADM (AF/swo, max4, address 0xecdc): LDMEQFD SP!, {R4, PC}
    if (asm & 0xffff8000) == 0x08bd8000:
        print("dangerous instruction 0x%x %s" % (i, InsString(i)))
        result = True

    return result


def put_in_bbl(i):
    return InsIsHanging(i) and not InsIsData(i) and not InsIsUnknown(i)


def associate_hanging_to_head(S, E):
    global stop
    result = False
    blocks = set()

    print("    hanging TAIL instructions to HEAD function...")

    #####
    # head -> tail
    # if the head is part of a function, and the tail is HANGING, add the tail
    # to that function if it is connected through a fallthrough edge

    for block in iter_blocks(S, E):
        if InsIsHanging(block.start_ea):
            continue

        # outgoing edges
        block_end = prev_head(block.end_ea)
        for i in BblOutgoingInsns(block_end):
            if not put_in_bbl(i):
                continue

            # at least one outgoing path is HANGING
            # print("block end at 0x%x, for 0x%x" % (block_end, i))
            blocks.add(block_end)
            break

    print("    initial %d blocks" % (len(blocks)))
    while len(blocks) > 0:
        addr = blocks.pop()

        # determine FT path
        decoded = DecodeInstruction(addr)
        if decoded is None:
            print("error, fallthrough decoded instruction not available 0x%x %s" % (addr, InsString(addr)))
            continue

        ft_insn = InsNext(decoded)

        for i in BblOutgoingInsns(addr):
            # only look at FT path because this surely should be put in the same function
            if i != ft_insn and not assoc_hanging_jump:
                continue
            if not put_in_bbl(i):
                continue

            block_start = i
            block_end = i
            # print("starting at 0x%x %d %d %d" % (i, idapython.InsIsHanging(i), idapython.InsIsData(i), idapython.InsIsUnknown(i)))
            # if i == 0xecd8:
            #     stop = True
            #     return False

            # find block end
            increment_end_address = True
            while True:
                end_decoded = DecodeInstruction(block_end)
                if end_decoded is None:
                    print("error, decoded instruction not available 0x%x %s" % (block_end, InsString(block_end)))
                    increment_end_address = False
                    break

                # this is a flow instruction, stop looking for block end here
                is_flow, _, _ = InsIsFlow(end_decoded)
                if is_flow and not dangerous_flow_instruction(block_end):
                    break

                outgoing = BblOutgoingInsns(block_end)
                if len(outgoing) == 0:
                    # e.g., UND #0xb7 followed by UNKNOWN bytes and has 0 outgoing instructions
                    break
                assert len(outgoing) == 1, "multiple outgoing from 0x%x %s" % (block_end, outgoing)
                dst = outgoing[0]

                if (block_end + end_decoded.size) != dst:
                    assert end_decoded.is_macro(),\
                        "end 0x%x, size %d, outgoing 0x%x" % (block_end, end_decoded.size, dst)
                    dst = block_end + end_decoded.size

                if not put_in_bbl(dst):
                    break

                block_end = dst

            # [start, end) for FunctionAppendChunk
            block_end_excl = block_end
            if increment_end_address:
                block_end_excl += DecodeInstruction(block_end).size

            if block_start == block_end_excl:
                # don't do anything if we have made an empty block
                continue

            # print("found block 0x%x to 0x%x-0x%x %d" % (addr, block_start, block_end, idapython.InsIsHanging(block_start)))
            FunctionAppendChunk(addr, block_start, block_end_excl)
            result = True
            blocks.add(block_end)

    return result


def associate_hanging_to_tail(S, E):
    result = False
    blocks = set()

    print("    hanging HEAD instructions to TAIL function...")

    #####
    # tail -> head
    # if the tail is part of a function, and the head is HANGING, add the head
    # block to the tail function

    # initial list of blocks that have at least 1 incoming 'hanging' edge
    for block in iter_blocks(S, E):
        if InsIsHanging(block.start_ea):
            continue

        for i in BblIncomingInsns(block.start_ea):
            if not InsIsHanging(i):
                continue

            # print("initial hanging block 0x%x" % (block.start_ea))
            blocks.add(block.start_ea)
            break

    print("    initial %d blocks" % (len(blocks)))
    while len(blocks) > 0:
        bbl_address = blocks.pop()

        for i in BblIncomingInsns(bbl_address):
            if not InsIsHanging(i):
                continue

            # block boundaries, IDA does not include the last address: [x, y)
            block_start = i

            i_decoded = DecodeInstruction(i)
            block_end = i + i_decoded.size

            # find block start
            while True:
                incoming_insns = BblIncomingInsns(block_start)

                # beginning of block?
                if len(incoming_insns) == 0:
                    break
                if len(incoming_insns) > 1:
                    break

                incoming = incoming_insns[0]
                if not InsIsHanging(incoming):
                    break

                decoded = DecodeInstruction(incoming)

                if InsIsBranch(decoded) or InsDefinesPC(decoded):
                    break
                if InsIsCall(decoded) and not (decoded.ea + decoded.size == block_start):
                    break

                block_start = incoming

            # print("found block 0x%x-0x%x to 0x%x" % (block_start, block_end, bbl_address))
            FunctionAppendChunk(bbl_address, block_start, block_end)
            result = True
            blocks.add(block_start)

    return result


def contains_colour_code(op):
    return color_codes_re.match(op)


def process_segment(S, E):
    global nr_hanging, function_id, block_id, edge_id

    #####
    # construct BBLs of hanging instructions and add them to existing functions
    if len(assoc_hanging_actions) > 0:
        print("associate hanging instructions...")

        it = 0
        keep_going = True
        while keep_going:
            keep_going = False
            # if it == 10:
            #      break

            print("  iteration %d" % it)

            # execute specified actions
            for action in assoc_hanging_actions:
                if action == 'h':
                    keep_going |= associate_hanging_to_head(S, E)
                elif action == 't':
                    keep_going |= associate_hanging_to_tail(S, E)

                if stop:
                    break

            if stop:
                break
            it += 1

    #####
    # move function chunks around if needed
    if switch_dest_to_tail:
        print("moving switch destinations to tail function...")

        # first construct basic blocks begin->end
        bbl_info = {}
        for block in iter_blocks(S, E):
            bbl_info[block.start_ea] = prev_head(block.end_ea)

        print("  recording move actions...")
        recorded_moves = []
        recorded_moves_statistics = {
            'case_to_multiple_functions': 0,
            'case_ends_in_call': 0
        }
        for insn in iter_all_insns(S, E):
            is_switch, switch_info, switch_cases = InsSwitchInfo(insn)

            # only look at switch instructions
            if not is_switch:
                continue

            switch_fun = get_func(insn)
            assert switch_fun is not None

            for range_index in range(SwitchNrCases(switch_info)):
                current_range = switch_cases.cases[range_index]
                assert len(current_range) == 1
                case_target = switch_cases.targets[range_index]

                if InsIsHanging(case_target):
                    print("switch 0x%x target 0x%x hangs" % (insn, case_target))
                    continue
                assert not InsIsHanging(case_target), "target hangs 0x%x" % case_target

                last_ins = bbl_info[case_target]
                if InsIsCall(DecodeInstruction(last_ins)):
                    print("switch 0x%x, target 0x%x ends in call" % (insn, case_target))
                    continue

                outgoing_functions = set()
                for dst in BblOutgoingInsns(last_ins):
                    outgoing_fun = get_func(dst)
                    if outgoing_fun is None:
                        print("no outgoing function for 0x%x" % dst)
                        continue
                    assert outgoing_fun is not None, "no function for 0x%x" % dst

                    if outgoing_fun != switch_fun:
                        # only look at interprocedural edges
                        outgoing_functions.add(outgoing_fun.start_ea)

                if len(outgoing_functions) > 1:
                    # print("switch 0x%x, target 0x%x has multiple outgoing functions, last instruction 0x%x: %s" % (insn, case_target, last_ins, outgoing_functions))
                    recorded_moves_statistics['case_to_multiple_functions'] += 1

                elif len(outgoing_functions) == 0:
                    # print("switch 0x%x, target 0x%x has no outgoing functions" % (insn, case_target))
                    recorded_moves_statistics['case_ends_in_call'] += 1

                else:
                    # move the target
                    recorded_moves.append({
                        'start': case_target,
                        'end': next_head(last_ins),
                        'dst': list(outgoing_functions)[0]
                    })

        print("  move action results: %s" % recorded_moves_statistics)

        # apply chunk moves
        print("executing %d move actions..." % (len(recorded_moves)))
        for move_command in recorded_moves:
            start = move_command['start']
            end_ex = move_command['end']
            function_addr = move_command['dst']

            Dechunkize(start, end_ex)
            FunctionAppendChunk(function_addr, start, end_ex)

    #####
    # still hanging instructions should be put in a list
    print("listing hanging instructions...")
    for insn in iter_hanging(S, E):
        # ifile.write("0x%x:%d:0x%x\n" % (insn, INVALID, 0xffffffff))
        nr_hanging += 1

    # hanging instructions belong to a special function
    # ffile.write("%d,(null)\n" % (INVALID))

    #####
    # process the CFG and emit a list of functions, bbls and instructions
    print("processing instructions...")
    for f in Functions(S, E):
        for block in iter_blocks_in_function(f):
            block_nins = 0

            #####
            # instructions
            # 'block.start_ea' is the address of the first instruction in the BBL
            # 'block.end_ea' is the address of the first instruction AFTER the BBL (last address + 4)
            for insn in iter_insns(block):
                if AddressInPLT(insn):
                    continue
                if not AddressInRange(S, E, insn):
                    continue

                # decode the instruction and construct sets of operand registers
                decoded_insn = DecodeInstruction(insn)
                if not decoded_insn:
                    print("error, no decoded instruction 0x%x in block 0x%x-0x%x" % (insn, block.start_ea, block.end_ea))
                    continue

                # print the instruction
                assem = InsAssembled(insn)
                operands = []
                k = 0
                op = print_operand(insn, k)
                while op:
                    assert not contains_colour_code(op)
                    operands.append(op)
                    k = k + 1
                    op = print_operand(insn, k)
                # dis = idapython.InsString(insn).split()
                mnem = print_insn_mnem(insn)
                ops = ';'.join(operands)
                last = 'TRUE' if InsIsLastInBbl(insn, block) else 'FALSE'
                ifile.write("%d,%d,%s,0x%s,%s,%s\n" % (insn, block_id, last, assem, mnem, ops))
                block_nins += 1

                # we want to consider _all_ possible branch instructions
                is_flow, is_call, is_branch = InsIsFlow(decoded_insn)
                if is_flow:
                    if not InsIsLastInBbl(insn, block):
                        """
                        if D.IsLoaded() and D.InsIsDispatcher(insn):
                            pass

                        elif idapython.InsIsCall(decoded_insn):
                            # calls are commonly placed in the middle of basic blocks by IDA
                            pass

                        elif D.IsLoaded() and D.InsIsOriginal(insn):
                            # sometimes an original switch instruction is not recognised by IDA as such
                            print("vanilla flow instruction not at end of BBL 0x%x-0x%x and is not call 0x%x %s, probably an unrecognised switch" % (block.startEA, block.endEA, insn, idapython.InsString(insn)))

                        elif D.IsLoaded() and D.InsIsData(insn):
                            print("DATA at 0x%x marked as flow instruction not at end of BBL 0x%x-0x%x %s" % (insn, block.startEA, block.endEA, idapython.InsString(insn)))

                        else:
                            # something is very wrong...
                            assert False, "flow instruction not at end of BBL 0x%x-0x%x and is not call 0x%x %s" % (block.startEA, block.endEA, insn, idapython.InsString(insn))
                        """
                        pass
                    #####
                    # outgoing edges

                    # take special care for conditional branches that have the fallthrough case identical to the jump case
                    drew_fallthrough = False
                    for dst in BblOutgoingInsns(insn):
                        # check destination section
                        dst_not_text, section_name = InsNotInTextSection(insn)
                        if dst_not_text:
                            print("0x%x -> 0x%x goes to section %s" % (insn, dst, section_name))
                            continue

                        gone = (dst != (insn + decoded_insn.size))

                        edge_type = EDGE_FALLTHROUGH
                        if is_call:
                            if gone:
                                src_fun_name = get_func_name(insn)
                                dst_name = get_func_name(dst)
                                plt = 'TRUE' if AddressInPLT(dst) else 'FALSE'
                                cfile.write("%d,%s,%d,%s,%s\n" % (insn, src_fun_name, dst, dst_name, plt))
                                # print_edge(block_id, insn, dst, edge_type, decoded_insn)

                        elif is_branch:
                            # print("JUMP 0x%x -> 0x%x, gone? %d" % (insn, dst, gone))
                            if InsIsConditional(decoded_insn):
                                edge_type = EDGE_JUMP if (gone or drew_fallthrough) else EDGE_FALLTHROUGH
                            else:
                                edge_type = EDGE_JUMP
                            print_edge(block_id, insn, dst, edge_type, decoded_insn)

                        else:
                            edge_type = EDGE_FALLTHROUGH
                            print_edge(block_id, insn, dst, edge_type, decoded_insn)

                        if edge_type == EDGE_FALLTHROUGH:
                            drew_fallthrough = True

                elif InsIsLastInBbl(insn, block):
                    # TODO: we can have 0 outgoing edges here, e.g. in the case where the instruction following
                    #       a switch instruction, which should be data (offset-based switch) is marked as code by IDA.
                    #       As such, IDA will try to draw FT edges from this DATA instruction to the next DATA.
                    done = False
                    for i in BblOutgoingInsns(insn):
                        # CHANGED assert not done, "0x%x" % (insn)
                        print_edge(block_id, insn, i, EDGE_FALLTHROUGH, decoded_insn)
                        done = True

            bfile.write("%d,%d,%d,%d\n" % (block_id, block.start_ea, block.end_ea, function_id))
            block_id += 1

        ffile.write("%d,%s\n" % (function_id, get_func_name(f).replace(":", "_")))
        function_id += 1


def parse_args(args):
    global assoc_hanging_actions, assoc_hanging_jump, switch_dest_to_tail, suffix

    try:
        opts, _ = getopt.getopt(args, "h:jsS:", ["hanging", "hanging-follow-jumps", "switch-dest-to-tail", "suffix"])
    except getopt.GetoptError as err:
        print("ERROR:", err)
        sys.exit(1)

    for opt, arg in opts:
        print("doing option %s with value %s" % (opt, arg))
        if opt in ("-h", "--hanging"):
            assoc_hanging_actions = arg
        elif opt in ("-j", "--hanging-follow-jumps"):
            assoc_hanging_jump = True
        elif opt in ("-s", "--switch-dest-to-tail"):
            switch_dest_to_tail = True
        elif opt in ("-S", "--suffix"):
            print("  suffix: %s" % arg)
            suffix = "." + arg


# create a list of instructions based on IDA's analysis results
def run_ida():
    global ifile, ffile, bfile, cfile, efile, stringfile, stringxreffile
    print("[SCRIPT] Waiting for ida to finish auto-analysis..")
    auto_wait()
    print("[SCRIPT] .. resuming script execution")
    parse_args(args()[1:])

    input_file = analysed_binary()
    basename = input_file + ".ida" + suffix

    # output files
    ifile = open(basename + ".instructions", "w")
    ifile.write("address,block_id,last,assembly,mnem,operands\n")
    print("Writing instructions to %s" % os.path.realpath(ifile.name))

    ffile = open(basename + ".functions", "w")
    ffile.write("id,name\n")
    print("Writing functions to %s" % os.path.realpath(ffile.name))

    bfile = open(basename + ".bbls", "w")
    bfile.write("id,startEA,endEA,function_id\n")
    print("Writing BBLs to %s" % os.path.realpath(bfile.name))

    cfile = open(basename + ".calls", "w")
    cfile.write("ins_address,ins_function_name,dest_address,dest_name,plt\n")
    print("Writing calls to %s" % os.path.realpath(cfile.name))

    efile = open(basename + ".edges", "w")
    efile.write("id,block_id,ins_address,dest_address,plt,edge_type\n")
    print("Writing edges to %s" % os.path.realpath(efile.name))

    stringfile = open(basename + ".strings", "w")
    stringfile.write("address,length,strtype,string\n")
    print("Writing strings to %s" % os.path.realpath(stringfile.name))

    stringxreffile = open(basename + ".stringxrefs", "w")
    stringxreffile.write("string_address,xref_addr\n")
    print("Writing string xrefs to %s" % os.path.realpath(stringxreffile.name))

    # statically linked binaries don't have a PLT section
    global plt_start, plt_end
    plt_start, plt_end = find_section(".plt")
    plt_got_start, plt_got_end = find_section(".plt.got")
    if plt_got_start != 0 and plt_got_end != 0:
        if plt_end == plt_got_start:
            plt_end = plt_got_end

    # we only look at the contents of the .text section
    S, E = find_section(".text")
    if S > 0:
        process_segment(S, E)

    S, E = find_section(".init")
    if S > 0:
        process_segment(S, E)

    # List all strings.
    for s in Strings():
        stringfile.write("%d,%d,%d,%s\n" % (s.ea, s.length, s.strtype, json.dumps(str(s))))

        # List all references to strings.
        for ref in DataRefsTo(s.ea):
            stringxreffile.write("%d,%d\n" % (s.ea, ref))

    ifile.close()
    ffile.close()
    bfile.close()
    efile.close()
    stringfile.close()
    stringxreffile.close()

    # make sure IDA exits, and does not show a GUI
    qexit(0)


def PLUGIN_ENTRY():
    print("plug entry")
    run_ida()


# when this script is loaded as the main program (i.e., a plugin in IDA),
# execute the 'run_ida' function
if __name__ == "__main__":
    print("running as plugin")
    run_ida()
else:
    run_ida()


repository: Accenture/VulFi
file: vulfi.py
url: https://github.com/Accenture/VulFi/blob/0bb7fdf8ccb906600cc209c35daf05774172acc8/vulfi.py
wanted_name: VulFi
comment: Vulnerability Finder
created_at: 2022-02-28 08:08:07+00:00
pushed_at: 2025-05-13 10:19:31+00:00
description:
IDA Pro plugin for query based searching within the binary useful mainly for vulnerability research.

content:
from ast import expr_context
from cmath import exp
import collections
from email.policy import default
from uuid import RESERVED_FUTURE
import idaapi
import idc
import ida_ua
import os
import json
import idautils
import ida_kernwin
import ida_name
import ida_hexrays
import ida_funcs
import traceback
if idaapi.IDA_SDK_VERSION >= 900:
    import ida_ida

icon = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00*\x00\x00\x00&\x08\x06\x00\x00\x00\xb2\x01\t \x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x02TIDATXG\xcd\x98\xcd.\x03Q\x14\xc7\xef\x10\x14-\xd5\xf8Jj!x\x00+,\xc5\x03\x88\x07\xb0\xb0\xb2\xea\x82\'\xc0\x13\x90\xb0\x15\x8f\x80x\x00+k\x89\xd8\x93\xb0\x94\xb0\x13A\xc6\xf9\xb7skz\xe7\xcc\xf4\xdc\xf9\xec/\xf9\xa5\x9d\x99\xce\xbdg\xce\xdc9\xf7N\x15\xb1@\x1e\x93\xf3\xd8\xe81\xaa\xe4\x91\xf7\xd9\xe4\x9etI\x04\xdc\x0b \xb0=\xf2\x89\xbc\xc0\x0e\r\xb2\x89@\xe1;\xb9C\x16\x05\xfaF\x80:\x9ee\xb2\x83KR\x1f\x84\xc8\xf2:\x99\x17\xe8K\xdfY\xed\x01\x19\xc0\x9fU\xbf\xb8\x80\xc0U\xa5\x08\xda\xbe%\xcd~qg\xdbc\xd3\x04\xe3\xc1<A\x9b\xf6\xf8E\x80h\x13\x01q\xfd\xb1\xd9\xd4\xe0\n\xb8\x93\xfcF6 \x00}D\x05\x081FC\xb3\xa9A#\xdc\xc9~1\x96l\x1f8I\x80Zq\xdb\xfe\xa7.J\x04\xbcEF\x81\x00\xf1\x1bI\x80\x10\xe3U\x0c\x1a\xe6\x1a\t\x13\x0f\x1c7apOr7\xad+\x8d4\xab~\xf10"`tf\x96;\x898\xc7\x1at\xc65\x96\x95\x18\x1a\xb1\xa7q\xae\xbeee\xa2\xf2\x97WV\x91\xcd\xae\xe5\xa8\x1b\x81\xb1\xd6glK\x1dp\x1c\xb7\x9fd\x8ea\x01\x92\x98\xc0\xd4\xeaxn\x0e\x97;\xf6G\xb9:Tj\x9e\xc3\x1c\x13\x15w)\x81\xa9\x15\x9d\xdeL\xd5\xdd\xdd\xf2x\xc7~\xceF\xa5\xea\x9e\xd5f\xc2\x02Mu\xa5\x86+\x0etrM\x81\xbe\xcd-\xb9\x1b\xa5\x91\xc01\xed\xf6\xe8\x98\xfbZ_tO\'\xa6\xb9\xe3\xa8\xb1"h\xb8\x89\xf8 OZ_\x83\x9c\xd7f\xd5\xda\xd0\xb0\xb7\xf5\xcf\xca`I\x1d\x8dO\xaa\x92C\xb9\xe4\xc1\xea]\x844P\xb0O>\xb7\xbe\xb6x\xfc\xfeRw_\x9f\xea\x81>\x1b\xe5\xaa\x1aq\xfe\x9bCp\x8d\xcaD\xfb7/\xbf?\xde\x916W\x9e\x99\x80\xf1\xc4\xdd\xc28ZO\x95\xb6\xc4\x99ZMcM\x95\xb6\xd8.XLQ\xdc\xb3|c\xe8 IV\x93.\xbc\xad\x88;\xb5&Zx\xc4%\xce\x82%\xd7lj0\xce\xb8`\xc2Lu\xaa\xb4%\xea\xad\xd5\xb4\x90lj\xd8\xa9\x95\x11Sea\xd9\xd4\x1c\x92\\p~3/\xee\x12\x90\xa9\xa8r\x95Kq\x97\x82\xf1\xc7\x05\ts+\xee\x12\xc2\xb2Z\xe8\x03\x14\x86\xb9`I\xe5=(+\xfcY\xed\xc9ljtV\x0b-\xeeR\xe2\xfc\x81V\x08\x19dR\xa9?"\x80\x16\n\xa6\x0c\x13@\x00\x00\x00\x00IEND\xaeB`\x82'
icon_id = idaapi.load_custom_icon(data=icon, format="png")

class utils:
    def create_dummy_hexrays_const(value):
        num = ida_hexrays.cexpr_t()
        num.op = ida_hexrays.cot_num
        num.n = ida_hexrays.cnumber_t()
        num.n._value = value
        return num

    def get_negative_disass(op):
        # Get negative value based on the size of the operand
        if op.dtype == 0x0: # Byte
            return -(((op.value & 0xff) ^ 0xff) + 1)
        elif op.dtype == 0x1: # Word
            return -(((op.value & 0xffff) ^ 0xffff) + 1)
        elif op.dtype == 0x2: # Dword
            return -(((op.value & 0xffffffff) ^ 0xffffffff) + 1)
        elif op.dtype == 0x7: # Qword
            return -((op.value ^ 0xffffffffffffffff) + 1)

    def get_func_name(ea):
        # Get pretty function name
        func_name = utils.get_pretty_func_name(idc.get_func_name(ea))
        if not func_name:
            func_name = utils.get_pretty_func_name(idc.get_name(ea))
        return func_name

    def get_pretty_func_name(name):
        # Demangle function name
        demangled_name = idc.demangle_name(name, idc.get_inf_attr(idc.INF_SHORT_DN))
        # Return as is if failed to demangle
        if not demangled_name:
            return name
        # Cut arguments
        return demangled_name[:demangled_name.find("(")]

    def prep_func_name(name):
        if name[0] != "." and name[0] != "_":
            # Name does not start with dot or underscore
            return [name,f".{name}",f"_{name}"]
        else:
            return [name[1:],f".{name[1:]}",f"_{name[1:]}"]

class null_after_visitor(ida_hexrays.ctree_visitor_t):
    def __init__(self,decompiled_function,call_xref,func_name,matched):
        self.found_call = False
        ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST) # CV_FAST does not keep parents nodes in CTREE
        self.decompiled_function = decompiled_function
        self.call_xref = call_xref
        self.func_name = func_name
        self.insn_counter = 0
        self.matched = matched

    def visit_insn(self, i):
        if self.found_call:
            self.insn_counter += 1
        return 0

    def visit_expr(self, e):
        if e.op == ida_hexrays.cot_call:
            xref_func_name = utils.get_func_name(e.x.obj_ea)
            if not xref_func_name:
                xref_func_name = idc.get_name(e.x.obj_ea)
            if xref_func_name.lower() == self.func_name.lower() and e.ea == self.call_xref:
                self.found_call = True
        if self.found_call and self.insn_counter < 2:
            if e.op == ida_hexrays.cot_asg:
                # The expression is assignment, check the right side of the assign
                if e.y.op == ida_hexrays.cot_num:
                    # The right side is number
                    if e.y.numval() == 0:
                        # Set to null
                        self.matched["set_to_null"] = True
        return 0



class VulFiScanner:
    def __init__(self,custom_rules=None):
        # Init class-wide variables
        self.functions_list = []
        if not custom_rules:
            with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"vulfi_rules.json"),"r") as rules_file:
                self.rules = json.load(rules_file)
        else:
            self.rules = custom_rules
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"vulfi_prototypes.json"),"r") as proto_file:
            self.prototypes = json.load(proto_file)
        # get pointer size
        if idaapi.IDA_SDK_VERSION >= 900:
            if ida_ida.inf_is_64bit():
                self.ptr_size = 8
            elif ida_ida.inf_is_32bit_exactly():
                self.ptr_size = 4
            else:
                self.ptr_size = 2
        else:
            info = idaapi.get_inf_structure()
            if info.is_64bit():
                self.ptr_size = 8
            elif info.is_32bit():
                self.ptr_size = 4
            else:
                self.ptr_size = 2
        # Get endianness
        if idaapi.IDA_SDK_VERSION >= 900:
            self.endian = "big" if ida_ida.inf_is_be() else "little"
        else:
            self.endian = "big" if idaapi.cvar.inf.is_be() else "little"
        # Check whether Hexrays can be used
        if not ida_hexrays.init_hexrays_plugin():
            self.hexrays = False
            self.strings_list = idautils.Strings()
        else:
            self.hexrays = True
            #self.strings_list = idautils.Strings()

    def start_scan(self,ignore_addr_list):
        results = []
        ida_kernwin.show_wait_box("VulFi scan running ... ")
        self.prepare_functions_list()
        for rule in self.rules:
            try:
                if rule["function_names"] == ["Array Access"]:
                    xrefs_dict = self.get_array_accesses()
                elif rule["function_names"] == ["Loop Check"]:
                    xrefs_dict = self.get_loops()
                else:
                    xrefs_dict = self.find_xrefs_by_name(rule["function_names"],rule["wrappers"])
            except:
                ida_kernwin.warning("This does not seem like a correct rule file. Aborting scan.")
                return None
            for scanned_function in xrefs_dict:
            # For each function in the rules
                skip_count = 0
                counter = 0 # For UI only
                total_xrefs = len(xrefs_dict[scanned_function]) # For UI only
                for scanned_function_xref_tuple in xrefs_dict[scanned_function]:
                    counter += 1
                    if ida_kernwin.user_cancelled():
                        print("[VulFi] Scan canceled!")
                        ida_kernwin.hide_wait_box()
                        return None
                    if skip_count > 0:
                        skip_count -= 1
                        continue
                    param = []
                    param_count = 0
                    function_call = None
                    scanned_function_xref = scanned_function_xref_tuple[0] # Address
                    scanned_function_name = scanned_function_xref_tuple[1] # Name
                    scanned_function_display_name = scanned_function_xref_tuple[2] # Display Name
                    # Update progress bar
                    ida_kernwin.replace_wait_box(f"Scanning {scanned_function_display_name} ({counter}/{total_xrefs})")
                    # If this rule is already verified for this address, move on
                    if f"{rule['name']}_{hex(scanned_function_xref)}" in ignore_addr_list:
                        continue
                    # For each xref to the function in the rules
                    # If ida_hexrays can be used eval the conditions in the rules file
                    if rule["function_names"] == ["Array Access"]:
                        param = [VulFiScanner.Param(self,scanned_function_xref_tuple[3],scanned_function_xref,scanned_function_name),VulFiScanner.Param(self,scanned_function_xref_tuple[4],scanned_function_xref,scanned_function_name)]
                    elif rule["function_names"] == ["Loop Check"]:
                        param = [VulFiScanner.Param(self,scanned_function_xref_tuple[3],scanned_function_xref,scanned_function_name),VulFiScanner.Param(self,scanned_function_xref_tuple[4],scanned_function_xref,scanned_function_name)]
                    else:
                        params_raw = self.get_xref_parameters(scanned_function_xref,scanned_function_name)
                        if params_raw is None:
                            if self.hexrays:
                                # Likely decompiler failure, we have to skip
                                continue
                            else:
                                # Params were not found, lets mark all XREFS
                                params_raw = []
                        for p in params_raw:
                            param.append(VulFiScanner.Param(self,p,scanned_function_xref,scanned_function_name))
                        param_count = len(param)

                    function_call = VulFiScanner.FunctionCall(self,scanned_function_xref,scanned_function_name)
                    # Name of the function where the xref is located
                    found_in_name = utils.get_func_name(scanned_function_xref)
                    priority = ""
                    # Every xref will be marked as Info in case that params fetch fails
                    try:
                        if not param:
                            priority = "Info"
                        elif eval(rule["mark_if"]["High"],dict(self=self, param=param,param_count=param_count,function_call=function_call)):
                            priority = "High"
                        elif eval(rule["mark_if"]["Medium"],dict(self=self, param=param,param_count=param_count,function_call=function_call)):
                            priority = "Medium"
                        elif eval(rule["mark_if"]["Low"],dict(self=self, param=param,param_count=param_count,function_call=function_call)):
                            priority = "Low"

                    except IndexError:
                        # Decompiler output has fewer parameters than the function prototype
                        # Mark the issue with Info priority
                        priority = "Info"
                        #results.append(list(VulFi.result_window_row(rule["name"],scanned_function_display_name,found_in_name,hex(scanned_function_xref),"Not Checked","Low","")))
                    except Exception:
                        print(traceback.format_exc())
                        ida_kernwin.warning(f"The rule \"{rule}\" is not valid!")
                        continue
                    # If the rule matched and is not wrapped:
                    #if priority and found_in_name and not "wrapped" in scanned_function_display_name:
                    if priority and not "wrapped" in scanned_function_display_name:
                        results.append(list(VulFi.result_window_row(rule["name"],scanned_function_display_name,found_in_name,hex(scanned_function_xref),"Not Checked",priority,"")))
                    elif "wrapped" in scanned_function_display_name and priority:
                        skip_count = 0 # rule for the wrapped function matched so no need to skip calls to the wrapper
                    elif "wrapped" in scanned_function_display_name and priority == "":
                        # Rule for wrapped function did not match, skip the wrapper
                        skip_count = int(scanned_function_display_name[scanned_function_display_name.find("wrapped:") + 8:-1])
        ida_kernwin.hide_wait_box()
        return results
    
    def get_loops(self):
         # Use the trick to extract loop paramters as members of the tuple
         # param[0] will be the counter variable and param[1] will be the check value
         loop_check_list = {"Loop Check":[]}
         if self.hexrays:
            for func in self.functions_list:
                try:
                    decompiled_function = ida_hexrays.decompile(func)
                    if decompiled_function is None:
                        # Decompilation failed
                        return loop_check_list
                    code = decompiled_function.pseudocode
                    for tree_item in decompiled_function.treeitems:
                        if tree_item.op == ida_hexrays.cit_while:
                            #print(f"[*] Found 'while' loop at: {hex(tree_item.ea)} ({ida_name.get_ea_name(func)})")
                            op = tree_item.to_specific_type.cwhile.expr.op
                            if (op >= ida_hexrays.cot_land and op <= ida_hexrays.cot_ult) or op in [ida_hexrays.cot_var, ida_hexrays.cot_lnot]:
                                # Not an infinte loop
                                counter, check_val = self.parse_condition(tree_item.to_specific_type.cwhile.expr)
                                loop_check_list["Loop Check"].append((tree_item.ea,"Loop Check","Loop Check",counter,check_val))
                            else:
                                counter, check_val = self.get_loop_check_val_break(tree_item.to_specific_type.cwhile.body)
                                loop_check_list["Loop Check"].append((tree_item.ea,"Loop Check","Loop Check",counter,check_val))
                        elif tree_item.op == ida_hexrays.cit_for:
                            #print(f"[*] Found 'for' loop at: {hex(tree_item.ea)} ({ida_name.get_ea_name(func)})")
                            if tree_item.to_specific_type.cfor.expr.op == ida_hexrays.cot_empty:
                                # Handle endless FOR loop
                                body = tree_item.to_specific_type.cfor.body
                                counter, check_val = self.get_loop_check_val_break(body)
                                loop_check_list["Loop Check"].append((tree_item.ea,"Loop Check","Loop Check",counter,check_val))
                            else:
                                counter = tree_item.to_specific_type.cfor.init.x
                                loop_check_list["Loop Check"].append((tree_item.ea,"Loop Check","Loop Check",counter,self.get_loop_check_val(counter,tree_item.to_specific_type.cfor.expr)))
                        elif tree_item.op == ida_hexrays.cit_do:
                            op = tree_item.to_specific_type.cdo.expr.op
                            if op >= ida_hexrays.cot_land and op <= ida_hexrays.cot_ult or op in [ida_hexrays.cot_var, ida_hexrays.cot_lnot]:
                                # Not an infinte loop
                                counter, check_val = self.parse_condition(tree_item.to_specific_type.cdo.expr)
                                loop_check_list["Loop Check"].append((tree_item.ea,"Loop Check","Loop Check",counter,check_val))
                            else:
                                counter, check_val = self.get_loop_check_val_break(tree_item.to_specific_type.cdo.body)
                                loop_check_list["Loop Check"].append((tree_item.ea,"Loop Check","Loop Check",counter,check_val))
                except Exception as e:
                    print(e)

         return loop_check_list
    
    def parse_condition(self,condition):
        if condition.op == ida_hexrays.cot_lnot:
            #(!x)
            return condition.x, utils.create_dummy_hexrays_const(0)
        if condition.op == ida_hexrays.cot_var or condition.op == ida_hexrays.cot_call or condition.op == ida_hexrays.cot_cast:
            #(x)
            return condition.x, utils.create_dummy_hexrays_const(1)
        if condition.op >= ida_hexrays.cot_eq and condition.op <= ida_hexrays.cot_ult:
            #(x > y)
            return condition.x, condition.y
        if condition.op == ida_hexrays.cot_lor or condition.op == ida_hexrays.cot_land:
            x_cond = self.parse_condition(condition.x)
            y_cond = self.parse_condition(condition.y)
            if y_cond[1].n:
                return y_cond
            return x_cond
    
    def get_loop_check_val(self,counter,comparison):
        x,y = self.parse_condition(comparison)
        if x == counter:
            return y
        # IDA places constant into the y operand for normal comparisons, for everything else we should return X to avoid marking that as a const
        return x

    def get_loop_check_val_break(self,loop_body):
        bd = loop_body.cblock.begin()
        while bd != loop_body.cblock.end():
            if bd.cur.op == ida_hexrays.cit_if:
                break_if = self.find_break(bd.cur.cif)
                if break_if:
                    return self.parse_condition(break_if.expr)
            bd.next()
        
        return None, None

    def find_break(self,if_insn):
        # TODO switch-case
        bt = if_insn.ithen.cblock.begin()
        while bt != if_insn.ithen.cblock.end():
            if bt.cur.op == ida_hexrays.cit_if:
                found_breaks = self.find_break(bt.cur.cif)
                if found_breaks:
                    return found_breaks
            elif bt.cur.op == ida_hexrays.cit_break or bt.cur.op == ida_hexrays.cit_return:
                return if_insn
            bt.next()
            
        if if_insn.ielse:  
            be = if_insn.ielse.cblock.begin()
            while be != if_insn.ielse.cblock.end():
                if be.cur.op == ida_hexrays.cit_if:
                    found_breaks = self.find_break(be.cur.cif)
                    if found_breaks:
                        return found_breaks
                elif be.cur.op == ida_hexrays.cit_break or bt.cur.op == ida_hexrays.cit_return:
                    return if_insn
                be.next()
        return None

    
    def get_array_accesses(self):
        array_access_list = {"Array Access":[]}
        if self.hexrays:
            for func in self.functions_list:
                try:
                    decompiled_function = ida_hexrays.decompile(func)
                    if decompiled_function is None:
                    # Decompilation failed
                        return array_access_list
                    code = decompiled_function.pseudocode
                    for citem in decompiled_function.treeitems:
                        if citem.op == ida_hexrays.cot_idx:
                            # Uses a trick by adding the index expression as a hidden 4th member of tuple
                            array_access_list["Array Access"].append((citem.ea,"Array Access","Array Access",citem.to_specific_type.x,citem.to_specific_type.y))
                except Exception as e:
                    pass
        return array_access_list

    def prepare_functions_list(self):
        self.functions_list = []
        # Gather all functions in all segments
        for segment in idautils.Segments():
            self.functions_list.extend(list(idautils.Functions(idc.get_segm_start(segment),idc.get_segm_end(segment))))

        # Gather imports
        def imports_callback(ea, name, ordinal):
            self.functions_list.append(ea)
            return True

        # For each import
        number_of_imports = idaapi.get_import_module_qty()
        for i in range(0, number_of_imports):
            idaapi.enum_import_names(i, imports_callback)

    # Returns a list of all xrefs to the function with specified name
    def find_xrefs_by_name(self,func_names,wrappers):
        xrefs_list = {}
        insn = ida_ua.insn_t()
        function_names = []
        for name in func_names:
            function_names.extend(utils.prep_func_name(name))

        # Convert function names to expected format (demangle them if possible)
        function_names = list(map(utils.get_pretty_func_name, function_names))

        # For all addresses of functions and imports check if it is function we are looking for
        for function_address in self.functions_list:
            current_function_name = utils.get_func_name(function_address)
            if not current_function_name:
                current_function_name = ida_name.get_ea_name(function_address)
            # If the type is not defined and the name of the function is known set the type
            prototype = self.prototypes.get(current_function_name.lower(), None)
            if prototype is not None:
                idc.SetType(function_address, prototype)
                idaapi.auto_wait()


            # Seach function "as is" and "ingnore case"
            if current_function_name not in function_names:
                current_function_name = current_function_name.lower()
                if current_function_name not in function_names:
                    continue

            # This is the function we are looking for
            if current_function_name not in xrefs_list.keys():
                xrefs_list[current_function_name] = []
            for xref in idautils.XrefsTo(function_address):
                # This rules out any functions within those that we are already looking at (wrapped)
                # It also makes sure that functions that are not xrefed within another function are not displayed
                xref_func_name = utils.get_func_name(xref.frm)
                if not xref_func_name or xref_func_name.lower() in function_names:
                    continue
                # Make sure that the instrution is indeed a call
                if ida_ua.decode_insn(insn,xref.frm) != idc.BADADDR:
                    if (insn.get_canon_feature() & 0x2 == 0x2 or # The instruction is a call
                    (insn.get_canon_feature() & 0xfff == 0x100 and insn.Op1.type in [idc.o_near,idc.o_far])): # The instruction uses first operand which is of type near/far immediate address
                        xref_tuple = (xref.frm,current_function_name,current_function_name)
                        if wrappers:
                            # If we should look for wrappers, do not includ the wrapped xref
                            retrieved_wrappers = self.get_wrapper_xrefs(xref.frm,current_function_name)
                            if not retrieved_wrappers:
                                # No wrappers
                                if not xref_tuple in xrefs_list[current_function_name]:
                                    xrefs_list[current_function_name].append(xref_tuple)
                            else:
                                # Wrappers were found
                                wrapped_tupple = (xref_tuple[0],xref_tuple[1],f"{xref_tuple[2]} (wrapped:{len(retrieved_wrappers)})")
                                xrefs_list[current_function_name].append(wrapped_tupple) # this makes sure that the wrapped function is right before its wrappers
                                xrefs_list[current_function_name].extend(retrieved_wrappers)
                        else:
                            if not xref_tuple in xrefs_list[current_function_name]:
                                xrefs_list[current_function_name].append(xref_tuple)
        return xrefs_list

    # Check if the xref can be a wrapper
    def get_wrapper_xrefs(self,xref,current_function_name):
        if self.hexrays:
            # Hexrays avaialble, we can use decompiler
            return self.get_wrapper_xrefs_hexrays(xref,current_function_name)
        else:
            # Hexrays not available, decompiler cannot be used
            return self.get_wrapper_xrefs_disass(xref)

    def get_wrapper_xrefs_hexrays(self,function_xref,current_function_name):
        wrapper_xrefs = []
        try:
            decompiled_function = ida_hexrays.decompile(function_xref)
        except:
            return wrapper_xrefs
        if decompiled_function:
            if decompiled_function is None:
                # Decompilation failed
                return wrapper_xrefs
            # Decompilation is fine
            code = decompiled_function.pseudocode
            for tree_item in decompiled_function.treeitems:
                if tree_item.ea == function_xref and tree_item.op == ida_hexrays.cot_call:
                    # Get called function name
                    xref_func_name = utils.get_func_name(tree_item.to_specific_type.x.obj_ea).lower()
                    if not xref_func_name:
                        xref_func_name = idc.get_name(tree_item.to_specific_type.x.obj_ea).lower()
                    if xref_func_name == current_function_name.lower():
                        # This is the correct call
                        lvars = list(decompiled_function.get_lvars()) # Get lvars
                        arg_objects = list(tree_item.to_specific_type.a)
                        found = True
                        while arg_objects:
                            current_obj = arg_objects.pop(0)
                            if current_obj is None:
                                continue
                            if current_obj.op == ida_hexrays.cot_var:
                                # if variable is not an arg_var we do not have a wrapper
                                if not lvars[current_obj.v.idx].is_arg_var:
                                    found = False
                            else:
                                arg_objects.extend([current_obj.to_specific_type.x,current_obj.to_specific_type.y])
                        # The function is likely a wrapper, get XREFs to it
                        if found:
                            for wrapper_xref in idautils.XrefsTo(decompiled_function.entry_ea):
                                wrapper_xref_func_name = utils.get_func_name(decompiled_function.entry_ea)
                                wrapper_xrefs.append((wrapper_xref.frm,wrapper_xref_func_name,f"{wrapper_xref_func_name} ({current_function_name} wrapper)"))
        return wrapper_xrefs


    # There probably is no architecture-agnostic way on how to do this without decompiler
    def get_wrapper_xrefs_disass(self,xref):
        return []

    # Wrapper for getting function params
    def get_xref_parameters(self,function_xref,scanned_function):
        if self.hexrays:
            # Hexrays avaialble, we can use decompiler
            return self.get_xref_parameters_hexrays(function_xref,scanned_function)
        else:
            # Hexrays not available, decompiler cannot be used
            return self.get_xref_parameters_disass(function_xref)

    # Returns list of ordered paramters from disassembly
    def get_xref_parameters_disass(self,function_xref):
        params_list = []
        # Requires the type to be already assigned to functions
        try:
            for param_ea in idaapi.get_arg_addrs(function_xref):
                if param_ea != idc.BADADDR:
                    param_insn = ida_ua.insn_t()
                    # decode the instruction linked to the parameter
                    if ida_ua.decode_insn(param_insn,param_ea) != idc.BADADDR:
                        if param_insn.get_canon_feature() & 0x00100 == 0x100:
                            # First operand - push
                            params_list.append(param_insn.Op1)
                        elif param_insn.get_canon_feature() & 0x00200 == 0x200:
                            # Second operands - mov/lea
                            params_list.append(param_insn.Op2)
                        elif param_insn.get_canon_feature() & 0x00400 == 0x400:
                            # Third operand
                            params_list.append(param_insn.Op3)
        except:
            return None
        return params_list

    # Returns an ordered list of workable object that represent each parameter of the function from decompiled code
    def get_xref_parameters_hexrays(self,function_xref,scanned_function):
        # Decompile function and find the call
        try:
            decompiled_function = ida_hexrays.decompile(function_xref)
        except:
            return None
        if decompiled_function is None:
            # Decompilation failed
            return None
        index = 0
        code = decompiled_function.pseudocode
        for tree_item in decompiled_function.treeitems:
            if tree_item.ea == function_xref and tree_item.op == ida_hexrays.cot_call:
                xref_func_name = utils.get_func_name(tree_item.to_specific_type.x.obj_ea).lower()
                if not xref_func_name:
                    xref_func_name = idc.get_name(tree_item.to_specific_type.x.obj_ea).lower()
                if xref_func_name == scanned_function.lower():
                    return list(tree_item.to_specific_type.a)
            index += 1
        # Call not found :(
        return None

    class Param:
        def __init__(self,scanner,param,call_xref,scanned_function):
            self.scanner_instance = scanner
            self.param = param
            self.call_xref = call_xref
            self.scanned_function = scanned_function

        def size(self):
            if self.scanner_instance.hexrays:
                return self.size_hexrays()
            else:
                return None
            
        def size_hexrays(self):
            try:
                if self.param.op == ida_hexrays.cot_cast:
                    param = self.param.x
                else:
                    param = self.param
                decompiled_function = ida_hexrays.decompile(self.call_xref)
                if decompiled_function is None:
                    # Decompilation failed
                    return None
                if decompiled_function:
                    code = decompiled_function.pseudocode
                    return decompiled_function.lvars[param.v.idx].width 
                return None
            except AttributeError:
                return None


        def used_as_index(self):
            if self.scanner_instance.hexrays:
                return self.used_as_index_hexrays()
            else:
                return False
            
        def used_as_index_hexrays(self):
            decompiled_function = ida_hexrays.decompile(self.call_xref)
            if decompiled_function is None:
                # Decompilation failed
                return False
            if decompiled_function:
                code = decompiled_function.pseudocode
                for citem in decompiled_function.treeitems:
                    if citem.op == ida_hexrays.cot_idx:
                        if self.param is not None:
                            if citem.to_specific_type.y == self.param:
                                return True
            return False

        def is_constant(self):
            if self.string_value() == "" and self.number_value() == None:
                if self.scanner_instance.hexrays and self.param and self.param.op == ida_hexrays.cot_ref:
                    return False
                asgs = self.__get_var_assignments()
                if asgs: # asgs will be empty with no hexrays
                    for asg in asgs:
                        if self.__is_before_call(asg.ea):
                            if self.string_value(asg.y) == "" and self.number_value(asg.y) == None:
                                # One of the assigns is non-const
                                return False
                    return True
                return False
            else:
                return True

        # Returns True if the param is used in any function call specified in the "function_list" parameter
        def used_in_call_before(self,function_list):
            if self.scanner_instance.hexrays:
                return self.used_in_call_before_hexrays(function_list)
            else:
                return self.used_in_call_before_disass(function_list)

        def used_in_call_before_hexrays(self,function_list):
            # get all calls with the parameter
            calls = self.__get_var_arg_calls()
            # prep function list
            tmp_fun_list = []
            for fun in function_list:
                tmp_fun_list.extend(utils.prep_func_name(fun))
            for call in calls:
                if utils.get_func_name(call.x.obj_ea) in tmp_fun_list and self.__is_before_call(call.ea):
                    return True
            return False

        def used_in_call_before_disass(self,function_list):
            return False

        def used_in_call_after(self,function_list):
            if self.scanner_instance.hexrays:
                return self.used_in_call_after_hexrays(function_list)
            else:
                return self.used_in_call_after_disass(function_list)

        def used_in_call_after_hexrays(self,function_list):
            # get all calls with the parameter
            calls = self.__get_var_arg_calls()
            # prep function list
            tmp_fun_list = []
            for fun in function_list:
                tmp_fun_list.extend(utils.prep_func_name(fun))
            for call in calls:
                if utils.get_func_name(call.x.obj_ea) in tmp_fun_list and not self.__is_before_call(call.ea):
                    return True
            return False
        
        def is_sign_compared(self):
            if self.scanner_instance.hexrays:
                return self.is_sign_compared_hexrays()
            else:
                return False


        def is_sign_compared_hexrays(self):
            decompiled_function = ida_hexrays.decompile(self.call_xref)
            if decompiled_function is None:
                # Decompilation failed
                return False
            if decompiled_function:
                code = decompiled_function.pseudocode
                for citem in decompiled_function.treeitems:
                    if citem.ea == self.call_xref:
                        parent = decompiled_function.body.find_parent_of(citem)
                        while parent:
                            if parent.op == ida_hexrays.cit_if:
                                comps = self.__get_signed_comparisons(parent)
                                if self.__is_var_used_in_comparison(comps):
                                    return True
                            parent = decompiled_function.body.find_parent_of(parent)
            return False
        
        def __is_var_used_in_comparison(self,comp_list):
            if self.param.op == ida_hexrays.cot_cast:
                param = self.param.x
            else:
                param = self.param
            ops = comp_list.copy()
            while ops:
                op = ops.pop()
                if not op:
                    continue
                if param == op:
                    return True
                ops.extend([op.x,op.y])
            return False
        
        def __get_signed_comparisons(self,citem):
            signed_comparisons = []
            signed_ops = [ida_hexrays.cot_sge, ida_hexrays.cot_sle, ida_hexrays.cot_sgt, ida_hexrays.cot_slt]
            exprs = [citem.to_specific_type.cif.expr]
            while exprs:
                expr = exprs.pop()
                if not expr:
                    continue
                if expr.op in signed_ops:
                    signed_comparisons.append(expr)
                else:
                    exprs.extend([expr.x,expr.y])
            return signed_comparisons


        def used_in_call_after_disass(self,function_list):
            return False

        # Simple check whether the given EA is before the call
        def __is_before_call(self,ea):
            func = idaapi.get_func(ea)
            flow = idaapi.FlowChart(func)
            call_block = None
            asg_block = None
            checked_blocks = []
            # Get block of the assignemnt and block of the call
            for block in flow:
                if ea >= block.start_ea and ea <= block.end_ea:
                    asg_block = block
                if self.call_xref >= block.start_ea and self.call_xref <= block.end_ea:
                    call_block = block
                    checked_blocks.append(block.start_ea)
            # If they are in the same block and asg ea is smaller then call_xref ea return True
            if call_block == asg_block:
                if ea < self.call_xref:
                    return True
            else:
                # Blocks are different
                call_preds = list(call_block.preds())
                while call_preds:
                    current_pred = call_preds.pop(0)
                    # Prevent endless loops
                    if current_pred.start_ea in checked_blocks:
                        continue
                    checked_blocks.append(current_pred.start_ea)
                    # Check if we matched the given assign block
                    if current_pred.start_ea == asg_block.start_ea:
                        return True
                    call_preds.extend(list(current_pred.preds()))
            return False

        def __get_var_arg_calls(self):
            calls = []
            # Parameter is cast, get x
            if self.param.op == ida_hexrays.cot_cast:
                param = self.param.x
            else:
                param = self.param
            decompiled_function = ida_hexrays.decompile(self.call_xref)
            if decompiled_function is None:
                # Decompilation failed
                return calls
            code = decompiled_function.pseudocode
            for citem in decompiled_function.treeitems:
                if citem.op == ida_hexrays.cot_call and citem.ea != self.call_xref: # skip calls we are tracing
                    # Potentially interesting call
                    for a in citem.to_specific_type.a:
                        expressions = [a,a.x,a.y,a.z]
                        while expressions:
                            current_expr = expressions.pop(0)
                            if current_expr:
                                if param == current_expr:
                                    # Call operation, add to array
                                    calls.append(citem.to_specific_type)
                                    break # we can break the loop as the variable was found within the arguments
                                expressions.extend([current_expr.x, current_expr.y, current_expr.z])
            return calls

        # Returns list of assign expressions for better accuracy
        def __get_var_assignments(self):
            asg = []
            # Parameter is cast, get x
            if self.param.op == ida_hexrays.cot_cast:
                param = self.param.x
            else:
                param = self.param
            decompiled_function = ida_hexrays.decompile(self.call_xref)
            if decompiled_function is None:
                # Decompilation failed
                return None
            code = decompiled_function.pseudocode
            for citem in decompiled_function.treeitems:
                if param == citem.to_specific_type:
                    parent = decompiled_function.body.find_parent_of(citem)
                    if parent.op >= ida_hexrays.cot_asg and parent.op <= ida_hexrays.cot_asgumod:
                        # Assign operation, add to array
                        asg.append(parent.to_specific_type)
            return asg

        def string_value(self,expr=None):
            if not expr:
                expr = self.param
            if not expr:
                return None
            if self.scanner_instance.hexrays: # hexrays
                string_val = idc.get_strlit_contents(expr.obj_ea)
                if string_val:
                    return string_val.decode()
                # If it is a cast (could happen)
                elif expr.op == ida_hexrays.cot_cast:
                    # If casted op is object
                    if expr.x.op == ida_hexrays.cot_obj:
                        # If that object points to a string
                        string_val = idc.get_strlit_contents(expr.x.obj_ea)
                        if string_val:
                            return string_val.decode()
                elif expr.op == ida_hexrays.cot_ref:
                    string_val = idc.get_strlit_contents(expr.x.obj_ea)
                    if string_val:
                        return string_val.decode()
                    if expr.x.op == ida_hexrays.cot_idx:
                        string_val = idc.get_strlit_contents(expr.x.x.obj_ea)
                        if string_val:
                            return string_val.decode()
                    # Check whether we are looking at CFString
                    if expr.x.obj_ea != idc.BADADDR:
                        c_str_pointer_value = idc.get_bytes(expr.x.obj_ea +  2* self.scanner_instance.ptr_size, self.scanner_instance.ptr_size)
                        tmp_ea = int.from_bytes(c_str_pointer_value,byteorder=self.scanner_instance.endian)
                        c_str_len = int.from_bytes(idc.get_bytes(expr.x.obj_ea +  3* self.scanner_instance.ptr_size, self.scanner_instance.ptr_size),byteorder=self.scanner_instance.endian)
                        c_string_value = idc.get_strlit_contents(tmp_ea)
                        # Having a string at this position and its length following the pointer suggests CFString struct
                        if c_string_value and len(c_string_value) == c_str_len:
                            # If this evaluates to string we have a constant
                            return c_string_value.decode()
                else:
                    # not a direct string
                    byte_value = idc.get_bytes(expr.obj_ea,self.scanner_instance.ptr_size)
                    if byte_value:
                        tmp_ea = int.from_bytes(byte_value,byteorder=self.scanner_instance.endian)
                        string_val = idc.get_strlit_contents(tmp_ea)
                        if string_val:
                            # If this evaluates to string we have a constant
                            return string_val.decode()
            else: # No hexrays
                if expr.type == 0x2:
                    # Reference
                    if idc.get_strlit_contents(expr.addr):
                        return idc.get_strlit_contents(expr.addr).decode()
                    else:
                        # Reference to reference
                        addr = int.from_bytes(idc.get_bytes(expr.addr, self.scanner_instance.ptr_size),byteorder=self.scanner_instance.endian)
                        if idc.get_strlit_contents(addr):
                            return idc.get_strlit_contents(addr).decode()

                if expr.type == 0x5:
                    if idc.get_strlit_contents(expr.value):
                        return idc.get_strlit_contents(expr.value).decode()
                # Reverse appoach of going from strings to calls
                for c_string in self.scanner_instance.strings_list:
                    for str_xref in idautils.XrefsTo(c_string.ea):
                        func = idaapi.get_func(str_xref.frm)
                        if func:
                            if func.start_ea == idaapi.get_func(self.call_xref).start_ea:
                                # The xref and the call are in the same function
                                # look if those are used within 5 instructions
                                if (str_xref.frm < self.call_xref):
                                    # XREF to STR is before the call to XREF
                                    instr_list = list(idautils.Heads(str_xref.frm,self.call_xref))
                                    if len(instr_list) <= 10:
                                        # No more then 10 instructions between the str XREF and the call
                                        for head in instr_list:
                                            current_insn = ida_ua.insn_t()
                                            if ida_ua.decode_insn(current_insn,head) != idc.BADADDR:
                                                if head != self.call_xref and current_insn.get_canon_feature() & 0x2 == 0x2:
                                                    # If it is not a target call but it is a call its false
                                                    return ""
                                        # If we survived the loop it is True
                                        return str(c_string)
            return ""

        def number_value(self,expr=None):
            if not expr:
                expr = self.param
            if not expr:
                return None
            if self.scanner_instance.hexrays: # hexrays
                # If it is number directly
                if expr.op == ida_hexrays.cot_num:
                    return expr.n._value
                # if it is a float
                elif expr.op == ida_hexrays.cot_fnum:
                    return expr.fpc.fnum.float
                # If it is a cast
                elif expr.op == ida_hexrays.cot_cast:
                    if expr.x.op == ida_hexrays.cot_num:
                        return expr.x.n._value
                    elif expr.x.op == ida_hexrays.cot_fnum:
                        return expr.x.fpc.fnum.float
            else: # No hexrays
                # self.param is op_t
                # 0x5 is immediate
                if expr.type == 0x5:
                    return expr.value
            return None

        def is_const_number(self,expr=None):
            if not expr:
                expr = self.param
            if self.scanner_instance.hexrays:
                if expr.op == ida_hexrays.cot_num or expr.op == ida_hexrays.cot_fnum:
                    return True
                elif expr.op == ida_hexrays.cot_cast and expr.x.op == ida_hexrays.cot_num:
                    return True
            else:
                if expr.type == 0x5:
                    return True
            return False

        # Wrapper for the basic UAF filter
        def set_to_null_after_call(self):
            if self.scanner_instance.hexrays:
                # Hexrays avaialble, we can use decompiler
                return self.set_to_null_after_call_hexrays()
            else:
                # Hexrays not available, decompiler cannot be used
                return self.set_to_null_after_call_disass()

        # Simple function that just checks whether the call is followed by an isntruction that sets the parameter to null
        # Note that this is very primitive to keep sort of architecture agnostic approach
        def set_to_null_after_call_disass(self):
            call_insn = ida_ua.insn_t()
            following_insn = ida_ua.insn_t()
            # First get the call instruction
            if ida_ua.decode_insn(call_insn,self.call_xref) != idc.BADADDR:
                # Now use the call instruction to get EA of next insn
                if ida_ua.decode_insn(following_insn,call_insn.ea + call_insn.size) != idc.BADADDR:
                    # following_insn can be used to evaluate whether Op2 is a constant or not
                    if following_insn.Op2.type == 0x5 and following_insn.Op2.value == 0x0:
                        # This should cover most of the cases where a mov instuction is used
                        return True
            return False

        # Using hexrays to figure out whether the function was followed by a null-set
        def set_to_null_after_call_hexrays(self):
            # get size of the call instruction, go past it and get expression that is linked to that address?
            matched = {"set_to_null":False}
            try:
                decompiled_function = ida_hexrays.decompile(self.call_xref)
            except:
                return matched["set_to_null"]
            if decompiled_function is None:
                # Decompilation failed
                return None
            custom_visitor = null_after_visitor(decompiled_function,self.call_xref,self.scanned_function,matched)
            custom_visitor.apply_to(decompiled_function.body, None)

            return matched["set_to_null"]



    class FunctionCall:
        def __init__(self,scanner,call_xref,scanned_function):
            self.scanner_instance = scanner
            self.call_xref = call_xref
            self.scanned_function = scanned_function

        
        def reachable_from(self,function_name):
            functions = [idaapi.get_func(self.call_xref)]
            checked_xrefs = []
            while functions:
                current_function = functions.pop(0)
                if current_function:
                    if utils.get_func_name(current_function.start_ea) in utils.prep_func_name(function_name):
                        return True
                    for xref in idautils.XrefsTo(current_function.start_ea):
                        if xref.frm not in checked_xrefs:
                            functions.append(idaapi.get_func(xref.frm))
                            checked_xrefs.append(xref.frm)
            return False

        # Check whether the return value of a function is part of some comparison (verification)
        def return_value_checked(self,check_val = None):
            if self.scanner_instance.hexrays:
                # Hexrays avaialble, we can use decompiler
                return self.return_value_checked_hexrays(check_val)
            else:
                # Hexrays not available, decompiler cannot be used
                return self.return_value_checked_disass(check_val)

        # Check if the return value of a function is verified after the call
        def return_value_checked_disass(self,check_val):
            # To stay architecture agnostic, this simply checks whether there is conditional jump within 5 instructions after the call
            for basic_block in idaapi.FlowChart(idaapi.get_func(self.call_xref)):
                if self.call_xref >= basic_block.start_ea and self.call_xref < basic_block.end_ea and len(list(basic_block.succs())) > 1:
                    # xref call belongs to this block
                    insn_counter = 0
                    current_insn = ida_ua.insn_t()
                    if ida_ua.decode_insn(current_insn,self.call_xref) != idc.BADADDR:
                        while insn_counter < 5:
                            following_insn = ida_ua.insn_t()
                            if ida_ua.decode_insn(following_insn,current_insn.ea + current_insn.size) != idc.BADADDR:
                                current_insn = following_insn
                                # Most of the compare instructions
                                if current_insn.get_canon_feature() & 0xffff == 0x300:
                                    if check_val is not None:
                                        for op in current_insn.ops:
                                            if op.type == 0x5:
                                                negative_value = utils.get_negative_disass(op)
                                                if op.value == check_val or negative_value == check_val:
                                                    return True
                                                else:
                                                    return False
                                    else:
                                        # Likely comparison instruction hit
                                        return True
                                insn_counter += 1
                            else:
                                break
                            # Jumped out of the block within 5 instructions
                            if current_insn.ea >= basic_block.end_ea:
                                return True
            return False

        def return_value_checked_hexrays(self,check_val):
            try:
                decompiled_function = ida_hexrays.decompile(self.call_xref)
            except:
                return None
            if decompiled_function is None:
                # Decompilation failed
                return None
            code = decompiled_function.pseudocode
            index = 0
            for tree_item in decompiled_function.treeitems:
                if tree_item.ea == self.call_xref and tree_item.op == ida_hexrays.cot_call:
                    xref_func_name = utils.get_func_name(tree_item.to_specific_type.x.obj_ea).lower()
                    if not xref_func_name:
                        xref_func_name = idc.get_name(tree_item.to_specific_type.x.obj_ea).lower()
                    if xref_func_name == self.scanned_function.lower():
                        parent = decompiled_function.body.find_parent_of(tree_item)
                        if parent.op == ida_hexrays.cot_cast: # return value is casted
                            parent = decompiled_function.body.find_parent_of(parent)
                        if (parent.op >= ida_hexrays.cot_eq and parent.op <= ida_hexrays.cot_ult):
                            if check_val is not None:
                                parent = parent.to_specific_type
                                if parent.y and (parent.y.n or parent.y.fpc): # Check if there is a Y operand and if it is a number, if it is, get its value
                                    if parent.y.n: # int
                                        value = parent.y.n._value
                                        negative_value = -((value ^ 0xffffffffffffffff) + 1)
                                    else: # float
                                        value = parent.y.fpc.fnum.float
                                        negative_value = value
                                    if negative_value == check_val or value == check_val:
                                        return True
                                    else:
                                        return False
                            else:
                                return True
                        elif parent.op == ida_hexrays.cit_if or parent.op == ida_hexrays.cot_lnot or parent.op == ida_hexrays.cot_lor or parent.op == ida_hexrays.cot_land:
                            if check_val is not None:
                                if not parent.to_specific_type.cif.expr.y:
                                    # There is no Y, likely checked against 0: if(func_call())
                                    if check_val == 0:
                                        return True
                                    else:
                                        return False
                            else:
                                return True
                        elif parent.op == ida_hexrays.cot_asg:
                            # return value is assigned to the variable/global
                            # Look through the rest of the function and find any if comparison with this variable and const number
                            if parent.to_specific_type.x.v or parent.to_specific_type.x.op == ida_hexrays.cot_obj:
                                for sub_tree_item in list(decompiled_function.treeitems)[index:]:
                                    if sub_tree_item.to_specific_type.op >= 22 and sub_tree_item.to_specific_type.op <= 31:
                                        # Comparison operator
                                        if (sub_tree_item.to_specific_type.x.v and sub_tree_item.to_specific_type.x.v.idx == parent.to_specific_type.x.v.idx) or (sub_tree_item.to_specific_type.x.obj_ea and sub_tree_item.to_specific_type.x.obj_ea == parent.to_specific_type.x.obj_ea):
                                            if check_val is not None:
                                                numeric_val = sub_tree_item.to_specific_type.y
                                                if numeric_val and (numeric_val.n or numeric_val.fpc): # Check if there is a Y operand and if it is a number, if it is, get its value
                                                    if numeric_val.n: # int
                                                        value = numeric_val.n._value
                                                        negative_value = -((value ^ 0xffffffffffffffff) + 1)
                                                    else: # float
                                                        value = numeric_val.fpc.fnum.float
                                                        negative_value = value
                                                    if negative_value == check_val or value == check_val:
                                                        return True
                                                    else:
                                                        return False
                                            else:
                                                return True
                                        else:
                                            # Look for embedded assigns in cit_if (look until cit_if or cit_block is found)
                                            embedded_parent = decompiled_function.body.find_parent_of(sub_tree_item)
                                            while True:
                                                if embedded_parent.op == ida_hexrays.cit_if:
                                                    return True
                                                elif embedded_parent.op == ida_hexrays.cit_block:
                                                    return False
                                                else:
                                                    embedded_parent = decompiled_function.body.find_parent_of(embedded_parent)
                                    elif sub_tree_item.op == ida_hexrays.cit_if or sub_tree_item.op == ida_hexrays.cot_lnot or sub_tree_item.op == ida_hexrays.cot_lor or sub_tree_item.op == ida_hexrays.cot_land:
                                        if check_val is not None:
                                            if not parent.is_expr():
                                                if not parent.to_specific_type.cif.expr.y:
                                                    # There is no Y, likely checked against 0: if(func_call())
                                                    if check_val == 0:
                                                        return True
                                                    else:
                                                        return False
                                        else:
                                            return True


                index += 1
            return False

class vulfi_form_t(ida_kernwin.Form):

    def __init__(self,function_name):
        F = ida_kernwin.Form
        F.__init__(
            self,
            r"""STARTITEM {id:rule_name_str}
BUTTON YES* Run
BUTTON CANCEL Cancel
Custom VulFi rule

{FormChangeCb}
Add custom rule to trace function: {function_name}
Custom rule name:
<#Name of the rule#:{rule_name_str}>
Custom Rule:
<#Rule as desribed in README#:{rule_str}>

""", {
            'function_name': F.StringLabel(function_name),
            'rule_name_str': F.StringInput(),
            'rule_str': F.StringInput(),
            'FormChangeCb': F.FormChangeCb(self.OnFormChange)
        })

    def OnFormChange(self, fid):
        return 1

class VulFi_Single_Function(idaapi.action_handler_t):
    result_window_title = "VulFi Results"
    result_window_columns_names = ["IssueName","FunctionName","FoundIn", "Address","Status", "Priority","Comment"]
    result_window_columns_sizes = [15,20,20,8,8,5,30]
    result_window_columns = [ list(column) for column in zip(result_window_columns_names,result_window_columns_sizes)]
    result_window_row = collections.namedtuple("VulFiResultRow",result_window_columns_names)

    def __init__(self,function_ea):
        idaapi.action_handler_t.__init__(self)
        self.function_ea = function_ea

    # Called when the button is clicked
    def activate(self, ctx):
        custom_rule = custom_rule_name = ""
        if not idc.get_type(self.function_ea) and (idaapi.get_func(self.function_ea) and not idc.get_type(idaapi.get_func(self.function_ea).start_ea)):
            # If the type of the function is not set notify the user
            answer = ida_kernwin.ask_buttons("Yes","No","Cancel",1,f"You should first set type for the function. Continue without type anyway?")
            if answer != 1:
                return

        # Show the form
        function_name = idc.get_func_name(self.function_ea)
        if not function_name:
            function_name = idc.get_name(self.function_ea)
        f = vulfi_form_t(function_name)
        # Compile (in order to populate the controls)
        f.Compile()
        # Execute the form
        ok = f.Execute()
        # If the form was confirmed
        if ok == 1:
            custom_rule_name = f.rule_name_str.value
            custom_rule = f.rule_str.value
        else:
            # Cancel
            return
        # Dispose the form
        f.Free()

        if not custom_rule or not custom_rule_name:
            ida_kernwin.warning("Both rule name and the rule have to be filled!")
            return
        # Craft a temporary rule here:
        tmp_rule = [{"name":f"{custom_rule_name}","function_names":[function_name.lower(),f".{function_name.lower()}"],"wrappers":False,"mark_if":{"High":custom_rule,"Medium":"False","Low":"False"}}]
        vulfi_scanner = VulFiScanner(tmp_rule)
        rows = []
        marked_addrs = []
        vulfi_data = {}
        # Load stored data
        node = idaapi.netnode()
        node.create("vulfi_data")
        if node.getblob(1,"S"):
            vulfi_data = json.loads(node.getblob(1,"S"))
        else:
            vulfi_data = {}
        for item in vulfi_data:
            rows.append([vulfi_data[item]["name"],vulfi_data[item]["function"],vulfi_data[item]["in"],vulfi_data[item]["addr"],vulfi_data[item]["status"],vulfi_data[item]["priority"],vulfi_data[item]["comment"]])
            marked_addrs.append(vulfi_data[item]["addr"])

        # Run the scan for selected function
        print("[VulFi] Started the scan ...")
        scan_result = vulfi_scanner.start_scan(marked_addrs)
        if scan_result is None:
            return
        rows.extend(scan_result)
        print("[VulFi] Scan done!")
        # Save the results
        for item in rows:
            vulfi_data[f"{item[3]}_{item[0]}"] = {"name":item[0],"function":item[1],"in":item[2],"addr":item[3],"status":item[4],"priority":item[5],"comment":item[6]}
        node.setblob(json.dumps(vulfi_data).encode("ascii"),1,"S")
        # Construct and show the form
        results_window = VulFiEmbeddedChooser(self.result_window_title,self.result_window_columns,rows,icon_id)
        results_window.AddCommand("Mark as False Positive", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Mark as Suspicious", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Mark as Vulnerable", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Set Vulfi Comment", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Remove Item(s)", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Export Results", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.Show()
        hooks.set_chooser(results_window)

    # This action is always available.
    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS

class vulfi_main_form_t(ida_kernwin.Form):

    def __init__(self):
        F = ida_kernwin.Form
        F.__init__(
            self,
            r"""STARTITEM {id:rDefault}
BUTTON YES* Run
BUTTON CANCEL Cancel
Custom VulFi rule

{FormChangeCb}
<##What rule set to use?##Default rules:{rDefault}>
<Custom rules:{rCustom}>
<Import previous results (JSON):{rImport}>{cType}>
<#Select a file to open#Browse to open:{iFileOpen}>

""", {
            'iFileOpen': F.FileInput(open=True),
            'cType': F.RadGroupControl(("rDefault", "rCustom","rImport")),
            'FormChangeCb': F.FormChangeCb(self.OnFormChange)
        })

    def OnFormChange(self, fid):
        if fid == -1 or fid == self.cType.id:
            if self.GetControlValue(self.cType) == 0:
                self.EnableField(self.iFileOpen, False)
            else:
                self.EnableField(self.iFileOpen, True)
        return 1

class VulFi(idaapi.action_handler_t):
    result_window_title = "VulFi Results"
    result_window_columns_names = ["IssueName","FunctionName","FoundIn", "Address","Status", "Priority","Comment"]
    result_window_columns_sizes = [15,20,20,8,8,5,30]
    result_window_columns = [ list(column) for column in zip(result_window_columns_names,result_window_columns_sizes)]
    result_window_row = collections.namedtuple("VulFiResultRow",result_window_columns_names)
    def __init__(self):
        idaapi.action_handler_t.__init__(self)

    # Called when the button is clicked
    def activate(self, ctx):
        answer = 0
        skip_scan = False
        rows = []
        vulfi_data = {}
        marked_addrs = []
        # Load stored data
        node = idaapi.netnode()
        node.create("vulfi_data")
        if node.getblob(1,"S"):
            vulfi_data = json.loads(node.getblob(1,"S"))
        else:
            vulfi_data = {}
        if vulfi_data:
            answer = ida_kernwin.ask_buttons("Load Existing","Scan Again","Cancel",1,f"Previous scan results found.")
        else:
            answer = 2
        if vulfi_data and answer == 1:
            for item in vulfi_data:
                rows.append([vulfi_data[item]["name"],vulfi_data[item]["function"],vulfi_data[item]["in"],vulfi_data[item]["addr"],vulfi_data[item]["status"],vulfi_data[item]["priority"],vulfi_data[item]["comment"]])
                marked_addrs.append(f'{vulfi_data[item]["name"]}_{vulfi_data[item]["addr"]}')
            print("[VulFi] Loading previous data.")
        elif answer == -1:
            # Cancel
            return
        else:
            # Show the form
            f = vulfi_main_form_t()
            # Compile (in order to populate the controls)
            f.Compile()
            # Execute the form
            ok = f.Execute()
            # If the form was confirmed
            if ok == 1:
                if f.cType.value == 0:
                    # Default scan
                    vulfi_scanner = VulFiScanner()
                elif f.cType.value == 1:
                    try:
                        with open(os.path.join(f.iFileOpen.value),"r") as rules_file:
                            vulfi_scanner = VulFiScanner(json.load(rules_file))
                    except:
                        ida_kernwin.warning("Failed to load custom rules!")
                        return
                else:
                    try:
                        with open(os.path.join(f.iFileOpen.value),"r") as import_file:
                            import_data = json.load(import_file)
                        for item in import_data["issues"]:
                            rows.append([item["IssueName"],item["FunctionName"],item["FoundIn"],item["Address"],item["Status"],item["Priority"],item["Comment"]])
                        skip_scan = True
                    except:
                        ida_kernwin.warning("Failed to load custom rules!")
                        return
            else:
                return

            for item in vulfi_data:
                rows.append([vulfi_data[item]["name"],vulfi_data[item]["function"],vulfi_data[item]["in"],vulfi_data[item]["addr"],vulfi_data[item]["status"],vulfi_data[item]["priority"],vulfi_data[item]["comment"]])
                marked_addrs.append(f'{vulfi_data[item]["name"]}_{vulfi_data[item]["addr"]}')
            # Run the scan
            if not skip_scan:
                print("[VulFi] Started the scan ...")
                scan_result = vulfi_scanner.start_scan(marked_addrs)
                if scan_result is None:
                    return
                rows.extend(scan_result)
                print("[VulFi] Scan done!")
            # Save the results
            for item in rows:
                vulfi_data[f"{item[3]}_{item[0]}"] = {"name":item[0],"function":item[1],"in":item[2],"addr":item[3],"status":item[4],"priority":item[5],"comment":item[6]}
            node.setblob(json.dumps(vulfi_data).encode("ascii"),1,"S")

        # Construct and show the form
        results_window = VulFiEmbeddedChooser(self.result_window_title,self.result_window_columns,rows,icon_id)
        results_window.AddCommand("Mark as False Positive", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Mark as Suspicious", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Mark as Vulnerable", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Set Vulfi Comment", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Remove Item(s)", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.AddCommand("Export Results", flags=4, menu_index=-1, icon=icon_id, emb=None, shortcut=None)
        results_window.Show()
        hooks.set_chooser(results_window)


    # This action is always available.
    def update(self, ctx):
        return idaapi.AST_ENABLE_ALWAYS

class vulfi_export_form_t(ida_kernwin.Form):

    def __init__(self):
        F = ida_kernwin.Form
        F.__init__(
            self,
            r"""STARTITEM {id:rJSON}
BUTTON YES* Save
BUTTON CANCEL Cancel
VulFi Results Export

{FormChangeCb}
<##Choose format for export##JSON:{rJSON}>
<CSV:{rCSV}>{cType}>
<#Select the output file#Select the output file:{iFileOpen}>

""", {
            'iFileOpen': F.FileInput(save=True),
            'cType': F.RadGroupControl(("rJSON", "rCSV")),
            'FormChangeCb': F.FormChangeCb(self.OnFormChange)
        })

    def OnFormChange(self,fid):
        return 1


class VulFiEmbeddedChooser(ida_kernwin.Choose):
    def __init__(self,title,columns,items,icon,embedded=False):
        ida_kernwin.Choose.__init__(self,title,columns,embedded=embedded,width=100,flags=ida_kernwin.Choose.CH_MULTI + ida_kernwin.Choose.CH_CAN_REFRESH)
        self.items = items
        self.icon = icon
        self.delete = False
        self.comment = False
        self.export = False

    def GetItems(self):
        return self.items

    def SetItems(self,items):
        if items is None:
            self.items = []
        else:
            self.items = items
        self.Refresh()

    def OnRefresh(self,n):
        for item in self.items:
            item[2] = utils.get_func_name(int(item[3],16))
        if self.delete:
            for i in reversed(n):
                self.items.pop(i)
            self.delete = False
            self.save()
        if self.comment:
            if len(n) == 1:
                comment = ida_kernwin.ask_str(self.items[n[0]][6],1,f"Enter the comment: ")
            else:
                comment = ida_kernwin.ask_str("",1,f"Enter the comment: ")
            for i in n:
                self.items[i][6] = comment
            self.comment = False
            self.save()
        if self.export:
            self.export = False
            self.vulfi_export()
            
        return n

    def save(self):
        # On close dumps the results
        vulfi_dict = {}
        for item in self.items:
            vulfi_dict[f"{item[3]}_{item[0]}"] = {"name":item[0],"function":item[1],"in":item[2],"addr":item[3],"status":item[4],"priority":item[5],"comment":item[6]}
        node = idaapi.netnode()
        node.create("vulfi_data")
        # Set the blob
        node.setblob(json.dumps(vulfi_dict).encode("ascii"),1,"S")

    def OnCommand(self,number,cmd_id):
        # Cmd_ids: 0 - FP, 1 - Susp, 2 - Vuln
        if cmd_id < 3:
            if cmd_id == 0:
                status = "False Positive"
            if cmd_id == 1:
                status = "Suspicious"
            if cmd_id == 2:
                status = "Vulnerable"
            # Item at index #3 is status
            self.items[number][4] = status
        if cmd_id == 3:
            # Comment
            self.comment = True
        if cmd_id == 4:
            # Delete selected items
            self.delete = True
        if cmd_id == 5:
            # Export
            self.export = True
            
        self.Refresh()
        # Save the data after every change
        self.save()

    def vulfi_export(self):
        # Show the form
        f = vulfi_export_form_t()
        # Compile (in order to populate the controls)
        f.Compile()
        # Execute the form
        ok = f.Execute()
        # If the form was confirmed
        if ok == 1:
            # Get file name
            file_name = f.iFileOpen.value
            if file_name:
                if f.cType.value == 0:
                    # JSON
                    # Pretify 
                    tmp_json = {"issues":[]}
                    for item in self.items:
                        tmp_json["issues"].append({
                            "IssueName": item[0],
                            "FunctionName": item[1],
                            "FoundIn": item[2],
                            "Address": item[3],
                            "Status": item[4],
                            "Priority": item[5],
                            "Comment": item[6]
                        })
                    with open(file_name,"w") as out_file:
                        json.dump(tmp_json, out_file)
                    ida_kernwin.info(f"Results exported in JSON format to {file_name}")
                else:
                    #CSV
                    csv_string = "IssueName,FunctionName,FoundIn,Address,Status,Priority,Comment\n"
                    for item in self.items:
                        csv_string += f"{item[0]},{item[1]},{item[2]},{item[3]},{item[4]},{item[5]},{item[6]}\n"
                    with open(file_name,"w") as out_file:
                        out_file.write(csv_string)
                    ida_kernwin.info(f"Results exported in comma-separated CSV file to {file_name}")
        

    def OnGetSize(self):
        return len(self.items)

    def OnSelectLine(self,number):
        # By default change to first selected line
        row = VulFi.result_window_row(*self.items[number[0]])
        destination = row.Address
        ida_kernwin.jumpto(int(destination,16))

    def OnGetLineAttr(self, number):
        if self.items[number][4] == "False Positive":
            return (0x9D9D9D,0)
        elif self.items[number][4] == "Suspicious":
            return (0xd0FF,0)
        elif self.items[number][4] == "Vulnerable":
            return (0xFF,0)

    def OnGetLine(self,number):
        try:
            return self.items[number]
        except:
            self.Refresh()
            return None


class vulfi_fetch_t(idaapi.plugin_t):
    comment = "Vulnerability Finder"
    help = "This script helps to reduce the amount of work required when inspecting potentially dangerous calls to functions such as 'memcpy', 'strcpy', etc."
    wanted_name = "VulFi"
    wanted_hotkey = ""
    flags = idaapi.PLUGIN_KEEP


    def init(self):
        vulfi_desc = idaapi.action_desc_t(
            'vulfi:fetch',   # The action name. This acts like an ID and must be unique
            'VulFi',  # The action text.
            VulFi(),   # The action handler.
            '',      # Optional: the action shortcut
            'Make VulFi fetch the potentially interesting places in binary.',  # Optional: the action tooltip (available in menus/toolbar)
            icon_id)           # Optional: the action icon (shows when in menus/toolbars)
        idaapi.register_action(vulfi_desc)
        idaapi.attach_action_to_menu("Search", "vulfi:fetch", idaapi.SETMENU_APP)

    def run(self):
        pass

    def term(self):
        pass



class Hooks(idaapi.UI_Hooks):
    def __init__(self):
        self.chooser = None
        idaapi.UI_Hooks.__init__(self)


    def finish_populating_widget_popup(self, form, popup):
        action_text = f"Add '{utils.get_func_name(idc.here())}' function to VulFi"
        function_ea = idc.here()
        try:
            # Get selected symbol
            selected_symbol, _ = ida_kernwin.get_highlight(ida_kernwin.get_current_viewer())
            # Check if it is a function name
            for function in idautils.Functions():
                if utils.get_func_name(function) in utils.prep_func_name(selected_symbol):
                    action_text = f"Add '{utils.get_func_name(function)}' function to VulFi"
                    function_ea = function
        except:
            pass
        action_desc = idaapi.action_desc_t(
        'vulfi:get_one',   # The action name. This acts like an ID and must be unique
        action_text,  # The action text.
        VulFi_Single_Function(function_ea),   # The action handler.
        '',      # Optional: the action shortcut
        'Make VulFi look for all interesting refences of this function.',  # Optional: the action tooltip (available in menus/toolbar)
        icon_id)           # Optional: the action icon (shows when in menus/toolbars)
        idaapi.unregister_action("vulfi:get_one")
        idaapi.register_action(action_desc)
        if ida_kernwin.get_widget_type(form) == idaapi.BWN_DISASM or ida_kernwin.get_widget_type(form) == idaapi.BWN_PSEUDOCODE:
            idaapi.attach_action_to_popup(form, popup, "vulfi:get_one", "")

    def current_widget_changed(self, widget, prev_widget):
        title = ida_kernwin.get_widget_title(widget)
        if title and "VulFi" in title and self.chooser:
            self.chooser.Refresh()

    def set_chooser(self,chooser):
        self.chooser = chooser

# Run the hooks
hooks = Hooks()
hooks.hook()

def PLUGIN_ENTRY():
    return vulfi_fetch_t()


repository: lasserre/ghidra
file: GhidraBuild/IDAPro/Python/7xx/plugins/xml_importer.py
description: Ghidra fork for PhD research
wanted_name: XML Importer
comment: Import XML PROGRAM file
created_at: 2022-10-27 17:50:30+00:00
pushed_at: 2024-07-04 21:22:22+00:00
url:
https://github.com/lasserre/ghidra/blob/1ab751ce459949eaa0457ea692018f550f8bd94e/GhidraBuild/IDAPro/Python/7xx/plugins/xml_importer.py

content:
## ###
#  IP: GHIDRA
# 
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#  
#       http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
##
#---------------------------------------------------------------------
# xmlimp.py - IDA XML Importer plugin
#---------------------------------------------------------------------
"""
Plugin for IDA to import a XML PROGRAM file into an existing open database.
This file must be placed in the IDA plugins directory.
The file idaxml.py must be placed in the IDA python directory.
"""

from __future__ import print_function
import ida_idaapi
import ida_pro
import idaxml
import idc
import sys

if sys.version_info.major >= 3:
    from idaxml import _exc_info
    sys.exc_value = lambda: _exc_info()[1]
    sys.exc_type = lambda: _exc_info()[0]

class XmlImporterPlugin(ida_idaapi.plugin_t):
    """
    XML Importer plugin class
    """
    flags = 0
    comment = "Import XML PROGRAM file"
    help = "Import XML <PROGRAM> document to database"
    wanted_name = "XML Importer"
    wanted_hotkey = "Ctrl-Alt-l"

    def init(self):
        """
        init function for XML Importer plugin.
        
        Returns:
            Constant PLUGIN_OK if this IDA version supports the plugin,
            else returns PLUGIN_SKIP if this IDA is older than the supported
            baseline version.
        """
        if idaxml.is_ida_version_supported():
            return ida_idaapi.PLUGIN_OK 
        else:
            return ida_idaapi.PLUGIN_SKIP


    def run(self, arg):
        """
        run function for XML Importer plugin.
        
        Args:
            arg: Integer, a non-zero value enables auto-run feature for
                 IDA batch (no gui) processing mode. Default is 0.
        """
        st = idc.set_ida_state(idc.IDA_STATUS_WORK)
        xml = idaxml.XmlImporter(idaxml.PLUGIN, arg)
        try:
            try:
                xml.import_xml()
            except idaxml.Cancelled:
                msg = "XML Import cancelled!"
                print("\n" + msg)
                idc.warning(msg)
            except idaxml.MultipleAddressSpacesNotSupported:
                msg  = "XML Import cancelled!"
                msg += "\n\nXML Import does not currently support"
                msg += "\nimporting multiple address spaces."
                print("\n" + msg)
                idc.warning(msg)
            except:
                msg = "***** Exception occurred: XML Importer failed! *****"
                print("\n" + msg + "\n", sys.exc_type, sys.exc_value)
                idc.warning(msg)
        finally:
            xml.cleanup()
            idc.set_ida_state(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlImporterPlugin()


repository: cheery/rollernote
file: main2.py
description: Music composition program
url: https://github.com/cheery/rollernote/blob/ebcf5d6a0544b8a07dae6787ea2e71f43eb69465/main2.py
created_at: 2024-08-22 10:32:19+00:00
pushed_at: 2025-03-23 18:13:52+00:00
content:
import aural.device
import aural.ladspa
from collections import namedtuple
import components3
import mido
import sdl2.ext
import entities
from visual import Widget, process_widgets
#import cairo
from aural import lv2
import aural as audio
#import commands
from visual import gui6
import math
from music import resolution
import bisect
import random
#import components
import subprocess
from fractions import Fraction
import os
#from immutables import Map

def get_tempo_envelope(document):
    default = random.randint(10, 200)
    for graph in document.track.graphs:
        if isinstance(graph, entities.Envelope) and graph.kind == 'tempo':
            env = resolution.linear_envelope(graph.segments, default)
            if env.check_positiveness():
                return env
    return resolution.LinearEnvelope([ (0, default, 0) ])

def setup_playback(document):
    bpm = get_tempo_envelope(document)
    return bpm, document.track.voices, dict((s.uid, s) for s in document.track.graphs)

from reaction import *
from aural import box
from aural.box import On, Off
from aural.miditunes import *
#from components3 import *

#ListInserted = namedtuple('ListInserted', ['index', 'value'])
#ListRemoved  = namedtuple('ListRemoved', ['value'])

#class ListControl:
#    def __init__(self, ui, contents):
#        self.modified = Source(ui.engine, as_stream)
#        self.contents = contents
#
#class ListView(gui3.Detail):
#    def __init__(self, control, func):
#        self.control = control
#        self.func    = func
#        self.table   = dict()
#
#    def attach(self, ui, this):
#        for value in self.control.contents:
#            frame = self.func(self.control, value)
#            this.contents.append(frame)
#            self.table[value] = frame
#        #@ui.engine.observe(self.control.modified)
#        #def _logic_(cmds):
#        #    if not cmds:
#        #        return
#        #    for cmd in cmds.value:
#        #        if isinstance(cmd, Inserted):
#        #            frame = self.func(self.control, cmd.key, dict(cmd.fields))
#        #            this.contents.append(frame)
#        #            self.table[cmd.key] = frame
#        #            frame.attach(ui, this)
#        #            ui.reconstrain()
#        #        if isinstance(cmd, Erased):
#        #            frame = self.table.pop(cmd.key)
#        #            this.contents.remove(frame)
#        #            frame.detach()
#        #            ui.reconstrain()
#
#import pandas as pd
#
#Inserted = namedtuple('Inserted', ['key', 'fields'])
#Erased   = namedtuple('Erased',   ['key'])
#Modified = namedtuple('Modified', ['key', 'name', 'value'])
#
## pd.read_csv(name, dtype=..., index_col=...)
## d.to_csv(name, index_label=...)
#
#class DatasetControl:
#    def __init__(self, ui, dataset):
#        self.modified = Source(ui.engine, as_stream)
#        self.dataset = dataset
#
#class DatasetView(gui3.Detail):
#    def __init__(self, control, func):
#        self.control = control
#        self.func    = func
#        self.table   = dict()
#
#    def attach(self, ui, this):
#        for key in self.control.dataset.index:
#            frame = self.func(self.control, key, dict(self.control.dataset.loc[key]))
#            this.contents.append(frame)
#            self.table[key] = frame
#        @ui.engine.observe(self.control.modified)
#        def _logic_(cmds):
#            if not cmds:
#                return
#            for cmd in cmds.value:
#                if isinstance(cmd, Inserted):
#                    frame = self.func(self.control, cmd.key, dict(cmd.fields))
#                    this.contents.append(frame)
#                    self.table[cmd.key] = frame
#                    frame.attach(ui, this)
#                    ui.reconstrain()
#                if isinstance(cmd, Erased):
#                    frame = self.table.pop(cmd.key)
#                    this.contents.remove(frame)
#                    frame.detach()
#                    ui.reconstrain()
#
#def demo(ui, editor):
#    dc = DatasetControl(ui, )
#
#    def _builder2_(control, key, fields):
#        @collect(control.modified)
#        def this_thing(mods):
#            out = []
#            for mod in mods:
#                if isinstance(mod, Modified) and mod.key == key:
#                    out.append(mod)
#            if out:
#                return Some(out)
#        def _modify_(fs, mods):
#            for mod in mods:
#                fs[mod.name] = mod.value
#            return fs
#        fields = Memory(fields, _modify_, this_thing)
#
#        mouse = gui3.MouseControl(ui)
#        pos = Hold((0,0), mouse.motion)
#        #bas = Hold((0,0), snapshot(mouse.left.down, pos)(lambda _, pos: pos))
#        @gui3.logic(pos,Hold(False,mouse.left.pressed), mouse.right.down)
#        def on_drag(ui, this, xy, pressed, right):
#            if pressed:
#                x, y = xy
#                x0,y0,w,h = this.parent.computed_box
#                x -= x0
#                y -= y0
#                control.modify(key, 'onset', x/800)
#                control.modify(key, 'offset', x/800+0.1)
#                note = y // 3
#                control.modify(key, 'note', max(0, min(127, note)))
#            if right:
#                dc.erase(key)
#        x0 = gui3.Variable()
#        x1 = gui3.Variable()
#        y0 = gui3.Variable()
#        y1 = gui3.Variable()
#        def f0(ui, this, solver, fields):
#            solver.addEditVariable(x0, 'strong')
#            solver.addEditVariable(x1, 'strong')
#            solver.addEditVariable(y0, 'strong')
#            solver.addEditVariable(y1, 'strong')
#            solver.addConstraint(this.left == x0 + this.parent.left)
#            solver.addConstraint(this.right == x1 + this.parent.left)
#            solver.addConstraint(this.top == y0 + this.parent.bottom)
#            solver.addConstraint(this.bottom == y1 + this.parent.bottom)
#        def f1(ui, this, solver, fields):
#            solver.suggestValue(x0, 800 * float(fields['onset']))
#            solver.suggestValue(x1, 800 * float(fields['offset']))
#            solver.suggestValue(y0, 3*(1 + int(fields['note'])))
#            solver.suggestValue(y1, 3*(0 + int(fields['note'])))
#        return gui3.Container([
#            on_drag,
#            gui3.CustomLayout(f0, f1, fields),
#            gui3.Column(),
#            gui3.trace((1,0,0,1)),
#            label(compute(fields)(lambda fields: repr(int(fields['note'])))),
#        ], mouse=mouse)
#    dc_mouse = gui3.MouseControl(ui)
#    pos = Hold((0,0), dc_mouse.motion)
#    @gui3.logic(dc_mouse.left.down, pos)
#    def dc_button(ui, this, left, xy):
#        if left:
#            x, y = xy
#            x -= this.left.value()
#            y -= this.bottom.value()
#            dc.insert({'onset': x/800,
#                       'offset': x/800+0.1,
#                       'note': (y//3),
#                       'velocity': 1.0})
#
#    def sweep_draw_setup(ui):
#        program = ui.mem(gui3.plain_line_program)
#        data = np.full(4, 0.0, dtype=np.float32)
#        buffer = ui.ctx.buffer(data)
#        vao = ui.ctx.vertex_array(program, buffer, 'point')
#        return program, vao, buffer, data
#
#    @gui3.drawing(ui.now)
#    def visualz(ui, this, now):
#        x,y,w,h = this.computed_box
#        t = (now % 5) / 5 * w
#        program, vao, buffer, data = ui.mem(sweep_draw_setup)
#        program['size'] = ui.widget.width, ui.widget.height
#        program['color'] = 1,0,0,1
#        data[0] = x + t
#        data[1] = y
#        data[2] = x + t
#        data[3] = y + h
#        buffer.write(data)
#        vao.render(mode=ui.ctx.LINES)
#        
#    dcv2 = gui3.Container([
#        dc_button,
#        visualz,
#        gui3.trace((0,0,0,1)),
#        gui3.Width(800),
#        gui3.Height(128*3),
#        DatasetView(dc, _builder2_),
#    ], mouse=dc_mouse)
#
#    w = 0.0
#    playing = {}
#    @ui.engine.observe(ui.now)
#    def _player_(now):
#        nonlocal w
#        t = (now % 5) / 5
#        if t < w:
#            w -= 1
#        assert w <= t
#        onsets = dc.dataset
#        for x in range(len(onsets)):
#            m = onsets.iloc[x]
#            onset = m['onset']
#            offset = m['offset']
#            note = m['note']
#            isect = max(w, onset) <= min(t, offset)
#            if not isect:
#                continue
#            offset = (offset - onset) + now
#            if note in playing:
#                playing[note] = max(playing[note], offset)
#            else:
#                playing[note] = offset
#                editor.audio_output.lock()
#                editor.fire_midi_keyboard(ui, On(note, 1.0))
#                editor.audio_output.unlock()
#        for note, offset in list(playing.items()):
#            if offset <= now:
#                playing.pop(note)
#                editor.audio_output.lock()
#                editor.fire_midi_keyboard(ui, Off(note))
#                editor.audio_output.unlock()
#        w = t
#
#    import os
#
#    def filefunc(files, entry):
#        if entry.is_dir():
#            color = (0.2, 0.2, 0.5, 1.0)
#            name = entry.name + "/"
#        else:
#            color = (0,0,0,1)
#            name = entry.name
#        return label(name, color=color, height=16)
#
#    return gui3.Container([
#        dcv2,
#        # textbox(textctl),
#        # button(ui, textctl.text),
#        # gui3.Container([
#        #     gui3.HAlign(0.0),
#        #     gui3.Row(),
#        #     virtual_keyboard(editor, ui),
#        #     oscilloscope(*editor.gui_channels),
#        #     vu_meter(ui, *editor.gui_channels),
#        #     clavier_visualizer(editor.gui_clavier, ui.now),
#        # ]),
#    ], keyboard = gui3.KeyboardControl(ui))

def demo(editor):
    ui = gui5.context.get()
    ui.ctx.enable(ui.ctx.BLEND)
    ui.ctx.blend_equation = ui.ctx.FUNC_ADD
    ui.ctx.blend_func = ui.ctx.SRC_ALPHA, ui.ctx.ONE_MINUS_SRC_ALPHA

    #clock(editor.time, (0,0, 150, 150))
    #gui5.fill((0,1,0,1), (150, 150, 50, 50))
    #gui5.fill((0,1,1,1), (200, 150, 50, 50))
    #gui5.fill((1,0,1,1), (150, 200, 50, 50))

    for index in editor.thing.index:
        fs = editor.thing.loc[index]
        onset  = fs['onset']
        offset = fs['offset']
        note   = fs['note']
        x0 = onset * 800
        x1 = offset * 800
        y0 = note * 8
        rect = (x0, y0, x1-x0, 8)
        gui5.fill((1,0,0,1), rect)
        ident = (50, index)
        if gui5.box(ui.mouse, rect):
            ui.hotitem = ident
            if ui.activeitem is None and ui.buttons == 1:
                ui.activeitem = ident
                ui.activestate = ui.mouse, onset, offset, note
        if ui.activeitem == ident:
            (x,y), onset, offset, note = ui.activestate
            editor.thing.loc[index, 'onset'] = (ui.mouse[0] - x) / 800 + onset
            editor.thing.loc[index, 'offset'] = (ui.mouse[0] - x) / 800 + offset
            nnote = (ui.mouse[1] - y) // 8 + note
            nnote = max(0, min(127, nnote))
            editor.thing.loc[index, 'note'] = nnote

    rect = (0,0,800,128*8)
    gui5.trace((0,0,0,1), rect)

    if gui5.box(ui.mouse, rect):
        ui.hotitem = 50
        if ui.activeitem is None and ui.buttons == 1:
            ui.activeitem = 50
            key = editor.thing.index.max()+1
            editor.thing.loc[key] = {
                'onset': (ui.mouse[0] - 0) / 800,
                'offset': (ui.mouse[0] - 0) / 800 + 0.1,
                'note': (ui.mouse[1] - 0) // 8,
                'velocity': 1.0
            }


# @gui4.composable
# def demo_bar(index, editor):
#     gui4.column(0)
#     gui4.trace((1,0,0,1))
#     note = int(editor.thing.loc[index]['note'])
#     label(repr(note))
# 
# 
# @gui4.composable
# def demo_row1(editor):
#     gui4.trace((1,0,0,1))
#     gui4.row(10)
#     colorbox[0]((0,1,0,1))
#     colorbox[1]((0,1,1,1))
#     colorbox[2]((1,0,1,1))
#     clock(editor.time)
#     scrollinglabel("Hello world", editor.time, 50)
#     testing = button[1]("Testing")
#     testing2 = button[2]("Testing2")
#     virtual_keyboard(editor)
#     oscilloscope(editor.out0, editor.out1)
#     vu_meter(editor.out0, editor.out1)
#     clavier_visualizer(editor)
# 
#     @gui4.logic(testing, testing2)
#     def _custom_logic_(ui, ident, layout, testing, testing2):
#         if testing:
#             print("hello")
#         if testing2:
#             print("world")
# 
# @gui4.composable
# def demo_row2(editor):
#     gui4.trace((1,0,0,1))
#     gui4.row(10)
#     demo_directory(editor)
#     text = gui4.state[1](value="hello")
#     textbox[1](text)
#     demo_thing(editor)
# 
# @gui4.composable
# def demo_directory(editor):
#     gui4.column(5)
#     for entry in editor.directory_contents:
#         if entry.is_dir():
#             color = (0.2, 0.2, 0.5, 1.0)
#             name = entry.name + "/"
#         else:
#             color = (0,0,0,1)
#             name = entry.name
#         label[name](name, color=color, height=16)
# 
# @gui4.composable
# def demo_thing(editor):
#     gui4.trace((0,0,0,1))
#     @gui4.layout
#     def _size_(cn, this):
#         cn(cn[this].width == 800)
#         cn(cn[this].height == 128*3)
#     for index in editor.thing.index:
#         demo_bar[index](index, editor)
#     @gui4.static
#     def layout_contents(layouter, this):
#         x,y,w,h = layouter[this].rect
#         for item in this.subframes:
#             index = item.key[1]
#             fs = editor.thing.loc[index]
#             onset = fs['onset']
#             offset = fs['offset']
#             note = fs['note']
#             layouter[item] = gui4.RigidLayout(int(x + onset*w), int(y + note*3),
#                                              int((offset - onset)*w), 3)
#             layouter.constrain(item)
#     @gui4.logic()
#     def button_press(ui, ident, layout):
#         if ui.inside:
#             ui.hotitem = ident
#             if ui.activeitem is None and ui.buttons > 0:
#                 ui.activeitem = ident
#                 key = editor.thing.index.max()+1
#                 editor.thing.loc[key] = {
#                   'onset': (ui.mouse[0] - layout.left) / layout.width,
#                   'offset': (ui.mouse[0] - layout.left) / layout.width + 0.1,
#                   'note': (ui.mouse[1] - layout.bottom) // 3,
#                   'velocity': 1.0
#                 }
# 
# @gui4.composable
# def demo_bar(index, editor):
#     gui4.column(0)
#     gui4.trace((1,0,0,1))
#     note = int(editor.thing.loc[index]['note'])
#     label(repr(note))
# 
# #        self.next_key = dataset.index.max()+1 if len(dataset) > 0 else 0
# #
# #    def insert(self, fields):
# #        key, self.next_key = self.next_key, self.next_key+1
# #        self.dataset.loc[key] = fields
# #        self.modified.send(Inserted(key, fields))
# #        return key
# #
# #    def erase(self, key):
# #        self.dataset = self.dataset.drop(key)
# #        self.modified.send(Erased(key))
# #
# #    def modify(self, key, name, value):
# #        self.dataset.loc[key, name] = value

class MainApp(gui6.Node):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.context_menus = []

    def pre_draw(self, ui, x, y):
        rect = self.rect.offset(x, y)
        gui6.fill(ui, (0.2, 0.2, 0.2, 1.0), rect)

    def open_context_menu(self, cmenu):
        self.context_menus.append(cmenu)
        self.add_child(cmenu)

    def close_context_menu(self, cmenu):
        index = self.context_menus.index(cmenu)
        for node in self.context_menus[index:]:
            node.detach()
        self.context_menus[index:] = []

import v2.avl
import v2.mid
import v2.rhythm
import v2.music.resolution
import v2.music.notes
import v2.widget

import aural.ladspa

from synth.entities import PrimType, signal, control
from synth.entities import Pipeline, OutPort, InPort, Constant, Knob, value_knob
from synth.entities import Template
from synth.instances import build_pipeline, Transport

value_updated = namedtuple("value_updated", [])
open_context_menu = namedtuple("open_context_menu", ['menu'])

main_pipe = Pipeline(1, "main",
  inputs = [ ], #OutPort(2, clavier) ],
  pipe = [
      Template(4, "sawtooth_1641", b"sawtooth_fc_oa",
         inputs = [ InPort(value_knob(0.92, 22050, 440.0, is_log=True), control) ],
         outputs = [ OutPort(3, signal) ])
  ],
  outputs = [ InPort(3, signal), InPort(3, signal) ],
  input_names = [ "clavier" ],
  output_names = [ "left", "right" ] )

def color_by_type(ty):
    if ty is control:
        return (1,1,0,1)
    elif ty is signal:
        return (1,0,0,1)
    else:
        return (1,1,1,1)

port_detach = namedtuple('port_detach', ['port'])

def close_context_menu(node):
    def _close_context_menu():
        node.root.close_context_menu(node)
    return _close_context_menu

@gui6.prefab(components3.ContextMenuSheet)
def guiport_context_menu(x, y, guiport):
    this = gui6.context.get()
    with components3.ContextMenu():
        gui6.position(gui6.Left, x)
        gui6.position(gui6.Top, y)
        if isinstance(guiport.port, InPort):
            with components3.Button() as button:
                button.clicked.connect(lambda: print('hello'))
                button.clicked.connect(close_context_menu(this))
                @button.clicked.connect
                def detach_port():
                    guiport.port.value = Constant(0)
                    guiport.updated.emit()
                gui6.padding(gui6.Horizontal, 10)
                components3.Label(text="detach",   color=(0,0,0,1))
            with components3.Button() as button:
                button.clicked.connect(close_context_menu(this))
                gui6.padding(gui6.Horizontal, 10)
                components3.Label(text="constant", color=(0,0,0,1))
            with components3.Button() as button:
                button.clicked.connect(close_context_menu(this))
                gui6.padding(gui6.Horizontal, 10)
                components3.Label(text="knob",     color=(0,0,0,1))

class GuiPort(gui6.Node):
    def __init__(self, *args, color, port, **kwargs):
        super().__init__(*args, **kwargs)
        self.color = color
        self.port  = port

    updated : gui6.Signal

    @property
    def value(self):
        if isinstance(self.port, InPort):
            return self.port.value

    def pre_draw(self, ui, x, y):
        rect = self.rect.offset(x, y)
        inrect0 = gui6.Rect(rect.left + 4, rect.bottom + 4, rect.width - 8, rect.height - 8)
        inrect1 = gui6.Rect(rect.left + 10, rect.bottom + 10, rect.width - 20, rect.height - 20)
        gui6.fill(ui, (0,0,0,1), rect)
        gui6.trace(ui, self.color, rect)
        if isinstance(self.port, OutPort):
            if ui.inside:
                ui.hotitem = self
                if ui.activeitem is None and ui.buttons == 1:
                    ui.activeitem = self
                    ui.activedrag = DragItem(color=(1,1,1,1), _attach_=False)
                    ui.activedrag.value = self.port
                    with gui6.NodeContextManager(ui.activedrag):
                        gui6.position_type(gui6.Absolute)
                        gui6.position(gui6.Left, ui.mouse[0])
                        gui6.position(gui6.Top, ui.widget.height - ui.mouse[1])
                        gui6.width(40.0)
                        gui6.height(40.0)
                    ui.queue(self.root.add_child, ui.activedrag)
        if isinstance(self.port, InPort) and ui.activeitem is not None and isinstance(ui.activedrag, DragItem) and isinstance(ui.activedrag.value, OutPort) and self.port.ty == ui.activedrag.value.ty:
            gui6.trace(ui, self.color, rect.outset(2,2))
            if ui.inside:
                gui6.trace(ui, self.color, rect.outset(4,4))
                if ui.buttons == 0:
                    self.port.value = ui.activedrag.value.uid
                    ui.queue(self.updated.emit)
        if isinstance(self.value, Knob):
            gui6.circle_stroke(ui, (0.5,0.5,0.5,1), inrect0)
            ratio = self.value.ratio
            x = rect.hcenter - math.sin(math.pi*(ratio * 1.5 + 0.25)) * inrect0.width/2
            y = rect.vcenter - math.cos(math.pi*(ratio * 1.5 + 0.25)) * inrect0.height/2
            gui6.circle_fill(ui, (1,1,1,1), (x-2,y-2,4,4))
            hc = rect.hcenter
            vc = rect.vcenter
            value = self.value.get_value()
            if -10 < value < 10.0:
                text = "%.2f" % value
            else: 
                text = "%.1f" % value
            font = ui.mem(FontEngine, int(9))
            width = font.measure(text)
            height = font.ascent + font.descent
            font.prepare((1,1,1,1))
            font.text(text, round(hc - width/2),
                            round(rect.bottom - height + font.descent))
            font.finish()
            if ui.inside:
                ui.hotitem = self
                if ui.activeitem is None and ui.buttons > 0:
                    ui.activeitem = self
                    ui.activestate = ui.mouse[1], self.value.ratio, ui.buttons
            if ui.activeitem is self and ui.buttons == 1:
                dy = ui.mouse[1] - ui.activestate[0]
                self.value.ratio = min(1.0, max(0.0, ui.activestate[1] + dy / 100.0))
                ui.queue(self.updated.emit)
        else:
            if ui.inside:
                ui.hotitem = self
                if ui.activeitem is None and ui.buttons > 0:
                    ui.activeitem = self
                    ui.activestate = ui.mouse[1], None, ui.buttons
            gui6.circle_stroke(ui, self.color, inrect0)
            gui6.circle_fill(ui, self.color, inrect1)
            if isinstance(self.value, Constant) and self.value.const != 0:
                hc = rect.hcenter
                vc = rect.vcenter
                value = self.value.const
                if -10 < value < 10.0:
                    text = "%.2f" % value
                else: 
                    text = "%.1f" % value
                font = ui.mem(FontEngine, int(9))
                width = font.measure(text)
                height = font.ascent + font.descent
                font.prepare((1,1,1,1))
                font.text(text, round(hc - width/2),
                                round(rect.bottom - height + font.descent))
                font.finish()
        if ui.buttons == 0 and ui.hotitem == self and ui.activeitem == self and ui.activestate[2] == 4:
            cmenu = guiport_context_menu(ui.mouse[0], ui.widget.height - ui.mouse[1], self)
            ui.queue(self.root.open_context_menu, cmenu)

def inport_column(names, ports, inline=False):
    with gui6.Node():
        gui6.flex_direction(gui6.Column)
        if not inline:
            gui6.margin(gui6.Left, 10.0)
            with gui6.Node():
                gui6.height(60)
        for i, port in enumerate(ports):
            with gui6.Node():
                gui6.flex_direction(gui6.Row)
                gui6.align_items(gui6.Center)
                with GuiPort(port=port, color=color_by_type(port.ty)):
                    gui6.width(32)
                    gui6.height(32)
                    gui6.margin(gui6.All, 10)
                components3.Label(text=names[i], color=(1,0,1,1), font_height=12)

def outport_column(names, ports, inline=False):
    with gui6.Node():
        gui6.flex_direction(gui6.Column)
        gui6.margin(gui6.Left, 10.0)
        if not inline:
            with gui6.Node():
                gui6.height(60)
        for i, port in enumerate(ports):
            with gui6.Node():
                gui6.flex_direction(gui6.Row)
                gui6.align_items(gui6.Center)
                components3.Label(text=names[i], color=(0,1,0,1), font_height=12)
                with GuiPort(name=port.uid, port=port, color=color_by_type(port.ty)):
                    gui6.width(32)
                    gui6.height(32)
                    gui6.margin(gui6.All, 10)

def plugin_template(locator, template):
    desc = locator.load(template.module, template.label)
    iports = [port for port in desc.info['ports'] if port['type'].startswith('input')]
    oports = [port for port in desc.info['ports'] if port['type'].startswith('output')]
    with gui6.Node():
        gui6.flex_direction(gui6.Column)
        with gui6.Node():
            gui6.height(60)
            gui6.margin(gui6.Horizontal, 8.0)
            components3.Label(text=template.module, color=(1,1,1,1), font_height=16)
            components3.Label(text=template.label.decode('utf-8'), color=(1,1,1,1), font_height=24)
        with gui6.Node():
            gui6.flex_direction(gui6.Row)
            inport_column([p['name'].decode('utf-8') for p in iports], template.inputs, inline=True)
            outport_column([p['name'].decode('utf-8') for p in oports], template.outputs, inline=True)

class Uids:
    def __init__(self, index=1000):
        self.index = index

    def __next__(self):
        self.index += 1
        return self.index
uids = Uids()

class PluginInfo:
    def __init__(self, module, label, info):
        self.module = module
        self.label = label
        self.info = info

    def to_template(self):
        inputs = [port for port in self.info['ports'] if port['type'].startswith('input')]
        outputs = [port for port in self.info['ports'] if port['type'].startswith('output')]
        inports = []
        for port in inputs:
            try:
                hint = port['hint']
                default = hint['default']
                if default is None:
                    value = Constant(0)
                else:
                    val = default['value']
                    if hint['lower'] is not None and hint['upper'] is not None:
                        lower = hint['lower'] or 0.0
                        upper = hint['upper'] or 0.0
                        if hint['sample_rate']: # TODO: fetch real sample rate somehow
                            lower *= 44100
                            upper *= 44100
                        if default['ratio']:
                            u = val
                            if hint['logarithmic']:
                                val = math.exp(math.log(lower) * (1-u) + math.log(upper) * u)
                            else:
                                val = lower * (1-u) + upper * u
                        value = value_knob(lower, upper, val, is_log=hint['logarithmic'])
                    else:
                        value = Constant(val)
            except ValueError:
                value = Constant(0)
            ty = signal if port['type'].endswith('*') else control
            inports.append(InPort(value, ty))
        outports = []
        for port in outputs:
            ty = signal if port['type'].endswith('*') else control
            outports.append(OutPort(next(uids), ty))
        return Template(next(uids), self.module, self.label, inports, outports)

class StageDropArea(gui6.Node):
    def __init__(self, *args, pipeline, index, **kwargs):
        super().__init__(*args, **kwargs)
        self.pipeline = pipeline
        self.index = index

    updated : gui6.Signal

    def pre_draw(self, ui, x, y):
        rect = self.rect.offset(x, y)
        if ui.activeitem is not None and isinstance(ui.activedrag, DragItem):
            if isinstance(ui.activedrag.value, PluginInfo):
                gui6.fill(ui, (0,1,1,1), rect.outset(-2,-2))
            if ui.inside:
                gui6.trace(ui, (0,1,1,1), rect.outset(4,4))
                if ui.buttons == 0:
                    tpl = ui.activedrag.value.to_template()
                    self.pipeline.pipe.insert(self.index, tpl)
                    ui.queue(self.updated.emit)

class PipelineEditor(gui6.Node):
    def __init__(self, *args, pipeline, audio_output, **kwargs):
        super().__init__(*args, **kwargs)
        self.pipeline = pipeline
        self.audio_output = audio_output
        self.redraw()
        self.update_edges()

    def redraw(self):
        self.detach_children()
        pipeline = self.pipeline
        audio_output = self.audio_output
        with gui6.NodeContextManager(self):
            gui6.flex_direction(gui6.Row)
            outport_column(pipeline.input_names, pipeline.inputs)
            with StageDropArea(pipeline=pipeline, index=0):
                gui6.width(10.0)
            for k, item in enumerate(pipeline.pipe, 1):
                if isinstance(item, Template):
                    plugin_template(audio_output.transport.locator, item)
                with StageDropArea(pipeline=pipeline, index=k):
                    gui6.width(10.0)
            inport_column(pipeline.output_names, pipeline.outputs)
        for node in self.traverse():
            if isinstance(node, GuiPort):
                node.updated.connect(self.update_pipeline)
            if isinstance(node, StageDropArea):
                node.updated.connect(self.redraw)
                node.updated.connect(self.update_pipeline)

    def update_pipeline(self):
        with self.audio_output:
            engine = self.audio_output.transport.engine
            locator = self.audio_output.transport.locator
            cache = self.audio_output.transport.pi.deconstruct()
            pi = build_pipeline(engine, locator, self.pipeline, [], cache)
            self.audio_output.transport.pi = pi
            self.audio_output.channels = pi.outputs
        self.update_edges()

    def update_edges(self):
        self.edges = {}
        for node in self.traverse():
            if isinstance(node, GuiPort) and isinstance(node.name, int):
                self.edges[node.name] = node, []
            if isinstance(node, GuiPort) and isinstance(node.value, int):
                self.edges[node.value][1].append(node)

    def pre_draw(self, ui, x, y):
        self.update_edges()
        rect = self.rect.offset(x, y)
        gui6.trace(ui, (1,1,1,1), rect)
        line_width = ui.ctx.line_width
        ui.ctx.line_width = 10.0
        for src, dests in self.edges.values():
            gr = src.global_rect
            x = gr.hcenter
            y = gr.vcenter
            for dst in dests:
                gr1 = dst.global_rect
                x1 = gr1.hcenter
                y1 = gr1.vcenter
                program, vao, buffer, data = ui.mem(components3.line_draw_setup)
                program['scroll'] = 0,0
                program['size'] = ui.widget.width, ui.widget.height
                program['color'] = src.color
                data[0] = x
                data[1] = y
                data[2] = x1
                data[3] = y1
                buffer.write(data)
                vao.render(mode=ui.ctx.LINES)
        ui.ctx.line_width = line_width

from visual.font import FontEngine

class KKnob(gui6.Node):
    def __init__(self, *args, info=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.info = info

    def pre_draw(self, ui, x, y):
      try:
        rect = self.rect.offset(x,y)
        port = self.info
        hint = port['hint']
        default = hint['default']
        if default is None:
            gui6.trace(ui, (1,0,0,1), rect)
        else:
            value = default['value']
            if hint['lower'] is None or hint['upper'] is None:
                gui6.trace(ui, (1,0,0,1), rect)
            else:
                gui6.circle_stroke(ui, (1,0,0,1), rect)
                lower = hint['lower'] or 0.0
                upper = hint['upper'] or 0.0
                if hint['sample_rate']:
                    lower *= 44100
                    upper *= 44100
                if default['ratio']:
                    u = value
                    if hint['logarithmic']:
                        if lower > 0 and upper > 0:
                            value = math.exp(math.log(lower) * u + math.log(upper) * (1-u))
                        else:
                            value = 0
                    else:
                        value = lower * u + upper * (1-u)
                else:
                    if hint['logarithmic']:
                        if lower > 0 and upper > 0:
                            u = math.log(value / upper) / math.log(lower / upper)
                        else:
                            u = 0
                    else:
                        u = (value - lower) / (upper - lower)
                x = rect.hcenter - math.sin(math.pi*(u * 1.5 + 0.25)) * rect.width/2
                y = rect.vcenter - math.cos(math.pi*(u * 1.5 + 0.25)) * rect.height/2
                gui6.circle_fill(ui, (1,1,1,1), (x-1,y-1,2,2))
            hc = rect.hcenter
            vc = rect.vcenter
            if hc != float('nan') and vc != float('nan'):
                text = "%.2f" % value
                font = ui.mem(FontEngine, int(12))
                width = font.measure(text)
                height = font.ascent + font.descent
                font.prepare((1,1,1,1))
                font.text(text, round(hc - width/2),
                                round(vc - height/2 + font.descent))
                font.finish()
      except ValueError:
        pass

class DragItem(components3.Border):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.mouse_visible = False
        self.value = None

    def pre_draw(self, ui, x, y):
        super().pre_draw(ui, x, y)
        if ui.activeitem is not None and ui.buttons > 0:
            with gui6.NodeContextManager(self):
                gui6.position(gui6.Left, ui.mouse[0])
                gui6.position(gui6.Top, ui.widget.height - ui.mouse[1])
        if ui.buttons == 0:
            ui.activedrag = None
            ui.queue(self.detach)

class DragSource(gui6.Node):
    def __init__(self, *args, value, **kwargs):
        super().__init__(*args, **kwargs)
        self.value = value

    def pre_draw(self, ui, x, y):
        if ui.inside:
            ui.hotitem = self
            if ui.activeitem is None and ui.buttons > 0:
                ui.activeitem = self
                ui.activedrag = DragItem(color=(1,1,1,1), _attach_=False)
                ui.activedrag.value = self.value
                with gui6.NodeContextManager(ui.activedrag):
                    gui6.position_type(gui6.Absolute)
                    gui6.position(gui6.Left, ui.mouse[0])
                    gui6.position(gui6.Top, ui.widget.height - ui.mouse[1])
                    gui6.width(40.0)
                    gui6.height(40.0)
                ui.queue(self.root.add_child, ui.activedrag)
                

@gui6.prefab(gui6.Node)
def plugin_entry(root, name, desc):
    with DragSource(value=PluginInfo(root, name, desc.info)):
        name = name.decode('utf-8')
        gui6.padding(gui6.All, 10.0)
        with gui6.Node():
            gui6.flex_direction(gui6.Column)
            components3.Label(text=root, color=(1,1,1,1), font_height=12)
            components3.Label(text=name, color=(1,1,1,1), font_height=16)
            with gui6.Node():
                gui6.flex_direction(gui6.Row)
                inputs = [port for port in desc.info['ports'] if port['type'].startswith('input')]
                outputs = [port for port in desc.info['ports'] if port['type'].startswith('output')]
                with gui6.Node():
                    gui6.flex_direction(gui6.Column)
                    for i in inputs:
                        _name = i['name'].decode('utf-8')
                        if i['type'].endswith('*'):
                            _name += '*'
                        with gui6.Node():
                            gui6.flex_direction(gui6.Row)
                            gui6.align_items(gui6.Center)
                            with KKnob(info=i):
                                gui6.width(32)
                                gui6.height(32)
                                gui6.margin(gui6.All, 10)
                            components3.Label(text=_name, color=(1,0,1,1), font_height=12)
                with gui6.Node():
                    gui6.width(10)
                with gui6.Node():
                    gui6.flex_direction(gui6.Column)
                    gui6.align_items(gui6.FlexEnd)
                    for i in outputs:
                        _name = i['name'].decode('utf-8')
                        if i['type'].endswith('*'):
                            _name += '*'
                        with gui6.Node():
                            gui6.flex_direction(gui6.Row)
                            gui6.align_items(gui6.Center)
                            components3.Label(text=_name, color=(0,1,0,1), font_height=12)
                            with KKnob(info=i):
                                gui6.width(32)
                                gui6.height(32)
                                gui6.margin(gui6.All, 10)

@gui6.prefab(MainApp)
def main_widget(editor):
    gui6.flex_direction(gui6.Column)
    with components3.Scrollable():
        with gui6.Node():
            gui6.position_type(gui6.Absolute)
            with PipelineEditor(name='pipeline_editor', pipeline=main_pipe, audio_output=editor.audio_output):
                pass
        gui6.margin(gui6.All, 10.0)
        gui6.flex_grow(1.0)
        gui6.flex_shrink(1.0)
    locator = editor.audio_output.transport.locator
    selection = list(locator.list())
    with components3.Scrollable(vertical = False) as this:
        gui6.flex_direction(gui6.Row)
        gui6.margin(gui6.All, 10.0)
        gui6.height(200)
        for i, (root, name, desc) in enumerate(selection):
            node = plugin_entry(root, name, desc)
            node.name = i
            this.add_child(node)

class Editor:
    def __init__(self):
        self.directory_contents = list(os.scandir("data/"))
        #self.thing = pd.DataFrame(
        #    data = { 'onset': pd.Series([0.1, 0.3], dtype='float32'),
        #             'offset': pd.Series([0.3, 0.6], dtype='float32'),
        #             'note': pd.Series([69, 80], dtype='uint16'),
        #             'velocity': pd.Series([1.0, 1.0], dtype='float32') },
        #    index = pd.Series([0,1], dtype='uint32'))

        locator = aural.ladspa.Locator()
        engine = aural.ladspa.Engine(sample_rate=44100, sample_count=4096)
        self.bay = bay = box.Bay(Engine(), engine, locator, pulse = Event())
        self.clavier = Source(bay.event_engine, as_stream)
        frame = Hold(0, bay.pulse)
        now = Compute(lambda frame: frame * engine.sample_step, [frame])

        ta = 1
        tih = 3 * ta / 4
        tempo = 90.0
        song1 = box.music(now, tempo, [
            (ta, {c4}),
            (ta, {e4, g4}),
            (ta, {e4, g4}),
        ], loop=True)
        song2 = box.music(now, tempo, [
            (tih, {f4}),
            (tih, {a4}),
            (tih, {b4}),
            (tih/2, {c5}),
            (tih/2, {e5}),
        ], loop=True)
        def merge_lists(x, y):
            match (x, y):
                case (Some(xs), Some(ys)):
                    return Some(xs+ys)
                case Some(xs):
                    return Some(xs)
                case Some(ys):
                    return Some(ys)
        song = Merge(song1, song2, merge_lists)

        # song = box.schedule(now, [
        #     (0.0,  On(52, 1.0)),
        #     (0.5,  Off(52)),
        #     (0.5,  On(59, 1.0)),
        #     (0.75, Off(59)),
        #     (0.75, On(54, 1.0)),
        #     (1.0,  Off(54)),
        # ], loop=1.0)

        #notes = Merge(self.clavier, song)
        notes = self.clavier

        def make_channel(bay, gate, trig, hz, velocity):
            modulator = box.sawtooth_fc(bay, Hold(100.0))
            hz  = Compute(lambda m, f: f / 4 + m*50.0, [modulator, hz])
            out = box.sawtooth_fa(bay, hz)
            env = box.adsr(bay, gate, trig, Hold(0.01), Hold(0.5), Hold(0.2), Hold(0.5))
            return Compute(lambda x, y: x * y, [out, env])

        def make_channel(bay, gate, trig, hz, velocity):
            modulator = box.sawtooth_fc(bay, Hold(800.0))
            hz  = Compute(lambda m, f: f / 2 + m*50.0, [modulator, hz])
            out = box.sawtooth_fa(bay, hz)
            env = box.adsr(bay, gate, trig, Hold(0.01), Hold(0.5), Hold(0.2), Hold(0.5))
            return Compute(lambda x, y: x * y, [out, env])

        # wobble = Compute(lambda now: 440.0 + math.sin(now * 2 * 1 * math.pi)*50.0, [now])
        # out = box.sin_fc_ac(bay, base_freq) 
        # out = box.sawtooth_fc(bay, base_freq) 

        #base_freq = box.as_frequency(69, notes)
        #out = box.monophonic(bay)(notes, make_channel)
        out = box.polyphonic(bay, now, voices=16)(notes, make_channel)
        #out = box.noise_white(bay, Hold(1.0))
        #out = Compute(lambda *xs: sum(xs), [out1, out2])

        lout = rout = out
        #out = box.tremolo(bay,
        #  frequency = Hold(100.0),
        #  depth_pc = Hold(60.0),
        #  gain = Hold(2.0),
        #  input = out)

        # left  = engine.zeros()
        # right = engine.zeros()
        # @bay.event_engine.observe(lout, rout)
        # def _listening_(ldata, rdata):
        #     left[:] = ldata
        #     right[:] = rdata
        # self.audio_output = aural.device.SDLDevice(bay, left, right)
        # self.out0 = engine.zeros()
        # self.out1 = engine.zeros()

        pi = build_pipeline(engine, locator, main_pipe, [], cache={})
        tr = Transport(engine, locator, pi)
        self.audio_output = aural.device.SDLDevice(tr, *pi.outputs)

        self.running = False
        self.time = 0.0
        self.widgets = dict()

        mido_in = mido.backend.get_input_names()[-1]
        self.midi_input = mido.backend.open_input(mido_in)

        self.dataset = dict(
            count = set([(0,)]),
            note = set([(0,), (1,), (4,), (6,)]),
            bar  = set([(4,), (8,), (12,)]),
        )

    def fire_midi_keyboard(self, ui, m):
        ui.clavier.append(m)
        self.clavier.send(m)

    def widget(self, *args):
        widget = Widget(*args)
        self.widgets[widget.uid] = widget
        return widget

    def ui(self):
        self.running = True
        sdl2.ext.init(video=True, audio=True)

        flags = sdl2.SDL_WINDOW_RESIZABLE | sdl2.SDL_WINDOW_OPENGL
        root = self.widget("rollernote", 1200, 700, flags, gui6.GUI, main_widget(self))

        sdl2.SDL_StartTextInput()

        while self.running:
            self.time = sdl2.SDL_GetTicks64() / 1000.0
            self.running = process_widgets(self.widgets, root)
            self.audio_output.lock()

            real_events = []
            for msg in self.midi_input.iter_pending():
                if msg.type == 'note_on':
                    self.fire_midi_keyboard(root.payload, On(msg.note, msg.velocity / 127))
                if msg.type == 'note_off':
                    self.fire_midi_keyboard(root.payload, Off(msg.note))

            self.out0 = self.audio_output.channels[0][:]
            self.out1 = self.audio_output.channels[1][:]
            self.audio_output.unlock()

        self.audio_output.close()
        sdl2.SDL_StopTextInput()
        #self.pluginhost.close()
        sdl2.ext.quit()

if __name__=='__main__':
    Editor().ui()


repository: tandasat/WinIoCtlDecoder
file: WinIoCtlDecoder.py
wanted_name: Windows IOCTL code decoder
created_at: 2013-12-20 21:46:53+00:00
pushed_at: 2023-12-01 05:00:24+00:00
description:
IDA Plugin which decodes Windows Device I/O control code into DeviceType, FunctionCode, AccessType and MethodType.

url:
https://github.com/tandasat/WinIoCtlDecoder/blob/c0f2a28f92d2d3dffff34fb765b7d3d933200454/WinIoCtlDecoder.py

content:
#!/usr/bin/python
"""Decodes a given 32-Bit Windows Device I/O control code

Author: Satoshi Tanda

Description:
    Decodes Windows Device I/O control code into DeviceType, FunctionCode,
    AccessType and MethodType.

Usage:
    1. Select an interesting IOCTL code in the disassemble window.
    2. Hit Ctrl+Alt+D or select Edit/Plugins/Windows IOCTL code decoder
    or
    Call winio_decode function directly from the Python CLI window.
        Python>winio_decode(0x220086)

Example:
    Python>winio_decode(0x220086)
    winio_decode(0x00220086)
    Device   : FILE_DEVICE_UNKNOWN (0x22)
    Function : 0x21
    Method   : METHOD_OUT_DIRECT (2)
    Access   : FILE_ANY_ACCESS (0)
"""

import sys
import idc
import idaapi


def winio_decode(ioctl_code):
    """Decodes IOCTL code and print it."""
    access_names = [
        "FILE_ANY_ACCESS",
        "FILE_READ_ACCESS",
        "FILE_WRITE_ACCESS",
        "FILE_READ_ACCESS | FILE_WRITE_ACCESS",
    ]
    method_names = [
        "METHOD_BUFFERED",
        "METHOD_IN_DIRECT",
        "METHOD_OUT_DIRECT",
        "METHOD_NEITHER",
    ]
    device_name_unknown = "<UNKNOWN>"
    device_names = [
        device_name_unknown,  # 0x00000000
        "FILE_DEVICE_BEEP",  # 0x00000001
        "FILE_DEVICE_CD_ROM",  # 0x00000002
        "FILE_DEVICE_CD_ROM_FILE_SYSTEM",  # 0x00000003
        "FILE_DEVICE_CONTROLLER",  # 0x00000004
        "FILE_DEVICE_DATALINK",  # 0x00000005
        "FILE_DEVICE_DFS",  # 0x00000006
        "FILE_DEVICE_DISK",  # 0x00000007
        "FILE_DEVICE_DISK_FILE_SYSTEM",  # 0x00000008
        "FILE_DEVICE_FILE_SYSTEM",  # 0x00000009
        "FILE_DEVICE_INPORT_PORT",  # 0x0000000a
        "FILE_DEVICE_KEYBOARD",  # 0x0000000b
        "FILE_DEVICE_MAILSLOT",  # 0x0000000c
        "FILE_DEVICE_MIDI_IN",  # 0x0000000d
        "FILE_DEVICE_MIDI_OUT",  # 0x0000000e
        "FILE_DEVICE_MOUSE",  # 0x0000000f
        "FILE_DEVICE_MULTI_UNC_PROVIDER",  # 0x00000010
        "FILE_DEVICE_NAMED_PIPE",  # 0x00000011
        "FILE_DEVICE_NETWORK",  # 0x00000012
        "FILE_DEVICE_NETWORK_BROWSER",  # 0x00000013
        "FILE_DEVICE_NETWORK_FILE_SYSTEM",  # 0x00000014
        "FILE_DEVICE_NULL",  # 0x00000015
        "FILE_DEVICE_PARALLEL_PORT",  # 0x00000016
        "FILE_DEVICE_PHYSICAL_NETCARD",  # 0x00000017
        "FILE_DEVICE_PRINTER",  # 0x00000018
        "FILE_DEVICE_SCANNER",  # 0x00000019
        "FILE_DEVICE_SERIAL_MOUSE_PORT",  # 0x0000001a
        "FILE_DEVICE_SERIAL_PORT",  # 0x0000001b
        "FILE_DEVICE_SCREEN",  # 0x0000001c
        "FILE_DEVICE_SOUND",  # 0x0000001d
        "FILE_DEVICE_STREAMS",  # 0x0000001e
        "FILE_DEVICE_TAPE",  # 0x0000001f
        "FILE_DEVICE_TAPE_FILE_SYSTEM",  # 0x00000020
        "FILE_DEVICE_TRANSPORT",  # 0x00000021
        "FILE_DEVICE_UNKNOWN",  # 0x00000022
        "FILE_DEVICE_VIDEO",  # 0x00000023
        "FILE_DEVICE_VIRTUAL_DISK",  # 0x00000024
        "FILE_DEVICE_WAVE_IN",  # 0x00000025
        "FILE_DEVICE_WAVE_OUT",  # 0x00000026
        "FILE_DEVICE_8042_PORT",  # 0x00000027
        "FILE_DEVICE_NETWORK_REDIRECTOR",  # 0x00000028
        "FILE_DEVICE_BATTERY",  # 0x00000029
        "FILE_DEVICE_BUS_EXTENDER",  # 0x0000002a
        "FILE_DEVICE_MODEM",  # 0x0000002b
        "FILE_DEVICE_VDM",  # 0x0000002c
        "FILE_DEVICE_MASS_STORAGE",  # 0x0000002d
        "FILE_DEVICE_SMB",  # 0x0000002e
        "FILE_DEVICE_KS",  # 0x0000002f
        "FILE_DEVICE_CHANGER",  # 0x00000030
        "FILE_DEVICE_SMARTCARD",  # 0x00000031
        "FILE_DEVICE_ACPI",  # 0x00000032
        "FILE_DEVICE_DVD",  # 0x00000033
        "FILE_DEVICE_FULLSCREEN_VIDEO",  # 0x00000034
        "FILE_DEVICE_DFS_FILE_SYSTEM",  # 0x00000035
        "FILE_DEVICE_DFS_VOLUME",  # 0x00000036
        "FILE_DEVICE_SERENUM",  # 0x00000037
        "FILE_DEVICE_TERMSRV",  # 0x00000038
        "FILE_DEVICE_KSEC",  # 0x00000039
        "FILE_DEVICE_FIPS",  # 0x0000003A
        "FILE_DEVICE_INFINIBAND",  # 0x0000003B
        device_name_unknown,  # 0x0000003C
        device_name_unknown,  # 0x0000003D
        "FILE_DEVICE_VMBUS",  # 0x0000003E
        "FILE_DEVICE_CRYPT_PROVIDER",  # 0x0000003F
        "FILE_DEVICE_WPD",  # 0x00000040
        "FILE_DEVICE_BLUETOOTH",  # 0x00000041
        "FILE_DEVICE_MT_COMPOSITE",  # 0x00000042
        "FILE_DEVICE_MT_TRANSPORT",  # 0x00000043
        "FILE_DEVICE_BIOMETRIC",  # 0x00000044
        "FILE_DEVICE_PMI",  # 0x00000045
        "FILE_DEVICE_EHSTOR",  # 00000046
        "FILE_DEVICE_DEVAPI",  # 00000047
        "FILE_DEVICE_GPIO",  # 00000048
        "FILE_DEVICE_USBEX",  # 00000049
        "FILE_DEVICE_CONSOLE",  # 00000050
        "FILE_DEVICE_NFP",  # 00000051
        "FILE_DEVICE_SYSENV",  # 00000052
        "FILE_DEVICE_VIRTUAL_BLOCK",  # 00000053
        "FILE_DEVICE_POINT_OF_SERVICE",  # 00000054
        "FILE_DEVICE_STORAGE_REPLICATION",  # 00000055
        "FILE_DEVICE_TRUST_ENV",  # 00000056
        "FILE_DEVICE_UCM",  # 00000057
        "FILE_DEVICE_UCMTCPCI",  # 00000058
        "FILE_DEVICE_PERSISTENT_MEMORY",  # 00000059
        "FILE_DEVICE_NVDIMM",  # 0000005a
        "FILE_DEVICE_HOLOGRAPHIC",  # 0000005b
        "FILE_DEVICE_SDFXHCI",  # 0000005c
        "FILE_DEVICE_UCMUCSI",  # 0000005d
        "FILE_DEVICE_PRM",  # 0000005e
        "FILE_DEVICE_EVENT_COLLECTOR",  # 0000005f
        "FILE_DEVICE_USB4",  # 00000060
        "FILE_DEVICE_SOUNDWIRE",  # 00000061
    ]
    device_names2 = [
        {"name": "MOUNTMGRCONTROLTYPE", "code": 0x0000006D},
    ]

    device = (ioctl_code >> 16) & 0xFFFF
    access = (ioctl_code >> 14) & 3
    function = (ioctl_code >> 2) & 0xFFF
    method = ioctl_code & 3

    if device >= len(device_names):
        device_name = device_name_unknown
        for dev in device_names2:
            if device == dev["code"]:
                device_name = dev["name"]
                break
    else:
        device_name = device_names[device]
    print(f"winio_decode(0x{ioctl_code:08X})")
    print(f"Device   : {device_name} ({device:X})")
    print(f"Function : 0x{function:X}")
    print(f"Method   : {method_names[method]} ({method})")
    print(f"Access   : {access_names[access]} ({access})")


class WinIoCtlPlugin(idaapi.plugin_t):
    """Class for IDA Pro plugin."""

    flags = idaapi.PLUGIN_UNL
    comment = (
        "Decodes Windows Device I/O control code into "
        + "DeviceType, FunctionCode, AccessType and MethodType."
    )
    help = ""
    wanted_name = "Windows IOCTL code decoder"
    wanted_hotkey = "Ctrl-Alt-D"

    def init(self):
        return idaapi.PLUGIN_OK

    def run(self, _=0):
        if idc.get_operand_type(idc.get_screen_ea(), 1) != 5:  # Immediate
            return
        value = idc.get_operand_value(idc.get_screen_ea(), 1) & 0xFFFFFFFF
        winio_decode(value)

    def term(self):
        pass


def PLUGIN_ENTRY():
    return WinIoCtlPlugin()


def main():
    if len(sys.argv) != 2:
        return
    winio_decode(int(sys.argv[1], 16))


if __name__ == "__main__":
    main()


repository: todzhang/EQGRP_Analysis
file: xorplugin/deobfuscate_plugin.py
description: 分析EQGRP的技巧
wanted_name: Xor Batch Deobfuscation
comment: Equation 字符串反混淆
created_at: 2022-03-21 06:01:47+00:00
pushed_at: 2024-03-10 04:26:00+00:00
url:
https://github.com/todzhang/EQGRP_Analysis/blob/f841ca0a4065b827cb8c4fd5f79772a3000927d5/xorplugin/deobfuscate_plugin.py

content:
# coding=utf-8
#!/bin/python3
# 这个插件用来去掉Equation的xor系列混淆
# 仅仅支持python 3，只在ida pro 7.7下测试过
# 将本文件放到IDA安装目录的plugins目录下，然后启动ida，在ida View中把光标放在解码函数开始，就可以在 Edit->Plugings->Xor Batch Deobfuscation
# 也可以使用快捷键Ctrl+Shift+D进行反混淆

import sys
try:
    import idaapi
    import idc
    import idautils
    import flare_emu
    # import hexdump
except ImportError:
    print("[FlareDeobfacatePlugin] Dependencies missing, Please check ida python and flare_emu is installed")
    sys.exit()

VERSION = "0.1.0"


def deobfuscate_function():
    # for xref in idautils.XrefsTo(idc.get_screen_ea(), 0):
               # print(xref.type, idautils.XrefTypeName(xref.type), 'from', hex(xref.frm), 'to', hex(xref.to))
    eh = flare_emu.EmuHelper()

    info = idaapi.get_inf_structure()
    if info.is_64bit():
        dx = "rdx"
        ax = "rax"
    else:
        dx = "edx"
        ax = "eax"


    ea = idc.get_screen_ea()
    for xref in idautils.XrefsTo(ea, 0):
        addr_call = xref.frm
        addr_before = idc.prev_head(addr_call) # 前一个指令
        addr_before = idc.prev_head(addr_before) # 前一个指令
        addr_after = idc.next_head(addr_call) # 后一个指令
        # 校验前一个指令是在传参，符合 mov eax, xxx
        if idc.print_insn_mnem(addr_before) == "mov" and idc.print_operand(addr_before, 0) == dx:
            #print("0x{:x} => 0x{:x}".format(addr_before, addr_call))
            eh.emulateRange(addr_before, endAddr=addr_after, skipCalls=False)
            ret = eh.getRegVal( ax )
            print( "decrypted at 0x%x: %s" %( addr_call ,eh.getEmuString(ret) ))
            # 设置注释
            idc.set_cmt(addr_call, "decrypted: " + eh.getEmuString(ret).decode(), 0)

    print ("Deobfuscated")


class XorPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC
    comment = "Equation 字符串反混淆"
    help = "一个小工具，用于反混淆Equation的字符串混淆"
    wanted_name = "Xor Batch Deobfuscation"
    # wanted_hotkey = "Ctrl+Shift+D"

    def init(self):
        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    def run(self, arg):
        deobfuscate_function()


def PLUGIN_ENTRY():
    return XorPlugin()


repository: Jinmo/palette-plugins
file: keypatch/plugins/keypatch.py
description: Palette plugins
wanted_name: Keypatch Patcher
comment: Keypatch plugin for IDA Pro (using Keystone framework)
created_at: 2019-03-03 07:45:35+00:00
pushed_at: 2019-04-28 13:39:53+00:00
url:
https://github.com/Jinmo/palette-plugins/blob/7b00707fedbe1031aa9b420ffbe4206db7883ebd/keypatch/plugins/keypatch.py

content:
# -*- coding: utf-8 -*-

# Keypatch IDA Plugin, powered by Keystone Engine (http://www.keystone-engine.org).
# By Nguyen Anh Quynh & Thanh Nguyen, 2018.

# Keypatch is released under the GPL v2. See COPYING for more information.
# Find docs & latest version at http://keystone-engine.org/keypatch

# This IDA plugin includes 3 tools inside: Patcher, Fill Range & Search.
# Access to these tools via menu "Edit | Keypatch", or via right-click popup menu "Keypatch".

# Hotkey Ctrl-Alt-K opens either Patcher or "Fill Range" window, depending on context.
#  - If there is no code selection, hotkey opens Patcher dialog
#  - If a range of code is selected, hotkey opens "Fill Range" dialog

# To revert (undo) the last patching, choose menu "Edit | Keypatch | Undo last patching".
# To check for update version, choose menu "Edit | Keypatch | Check for update".

'''
    Do not use shortcut(Ctrk + Alt + K) for debugging, otherwise debugging features will not be available.
'''
#########################################################################################################
is_debug = False
if is_debug:
    ''' 
        Install pydevd:

        1. sudo pip install pydevd

        or

        2. Install pycharm-debug.egg, Ensure to use pycharm pro
        https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html

        # import site
        # site.addsitedir("/usr/local/lib/python2.7/site-packages")
    '''
    try:
        import pydevd

        pydevd.settrace(host='localhost',
                        port=51234,
                        stdoutToServer=True,
                        stderrToServer=True
                        )
    except Exception as e:
        print(e)
#########################################################################################################

import os
import re
import json
from keystone import *
import idc
import idaapi
from idc import GetOpType, GetOpnd, ItemEnd

# bleeding-edge version
# on a new release, this should be sync with VERSION_STABLE file
VERSION = "2.2"


MAX_INSTRUCTION_STRLEN = 256
MAX_ENCODING_LEN = 40
MAX_ADDRESS_LEN = 40
ENCODING_ERR_OUTPUT = "..."
KP_GITHUB_VERSION = "https://raw.githubusercontent.com/keystone-engine/keypatch/master/VERSION_STABLE"
KP_HOMEPAGE = "http://keystone-engine.org/keypatch"

X86_NOP = "\x90"

# Configuration file
KP_CFGFILE = os.path.join(idaapi.get_user_idadir(), "keypatch.cfg")

# save all the info on patching
patch_info = []


def to_hexstr(buf, sep=' '):
    return sep.join("{0:02x}".format(ord(c)) for c in buf).upper()


# return a normalized code, or None if input is invalid
def convert_hexstr(code):
    # normalize code
    code = code.lower()
    code = code.replace(' ', '')    # remove space
    code = code.replace('h', '')    # remove trailing 'h' in 90h
    code = code.replace('0x', '')   # remove 0x
    code = code.replace('\\x', '')  # remove \x
    code = code.replace(',', '')    # remove ,
    code = code.replace(';', '')    # remove ;
    code = code.replace('"', '')    # remove "
    code = code.replace("'", '')    # remove '
    code = code.replace("+", '')    # remove +

    # single-digit hexcode?
    if len(code) == 1 and ((code >= '0' and code <= '9') or (code >= 'a' and code <= 'f')):
        # stick 0 in front (so 'a' --> '0a')
        code = '0' + code

    # odd-length is invalid
    if len(code) % 2 != 0:
        return None

    try:
        hex_data = code.decode('hex')
        # we want a list of int
        return [ord(i) for i in hex_data]
    except:
        # invalid hex
        return None


# download a file from @url, then return (result, file-content)
# return (0, content) on success, or ({1|2}, None) on download failure
def url_download(url):
    from urllib2 import Request, urlopen, URLError, HTTPError

    # create the url and the request
    req = Request(url)

    # Open the url
    try:
        # download this URL
        f = urlopen(req)
        content = f.read()
        return (0, content)

    # handle errors
    except HTTPError, e:
        # print "HTTP Error:", e.code , url
        # fail to download this file
        return (1, None)
    except URLError, e:
        # print "URL Error:", e.reason , url
        # fail to download this file
        return (1, None)
    except Exception as e:
        # fail to save the downloaded file
        # print("Error:", e)
        return (2, None)


def get_name_value(_from, name):
    """
    Fixed: the return value truncated(32 bit) of get_name_value function that analyzed 64 bit binary file about ida64 for win.

    eg:
    type == idaapi.NT_BYTE
    (type, value) = idaapi.get_name_value(idc.BADADDR, "wcschr") # ida64 for win

    value = 0x14003d3f0L is correct  ida64 > 7.x for macOS
    value = 0x4003d3f0L is truncated ida64 >= 6.x for win, ida64 == 6.x for macOS

    :param _from: ea
    :param name: name string
    :return: tuple
    """
    (type, value) = idaapi.get_name_value(_from, name)
    if type == idaapi.NT_BYTE:  # type is byte name (regular name)
        value = idaapi.get_name_ea(_from, name)
    return (type, value)


## Main Keypatch class
class Keypatch_Asm:
    # supported architectures
    arch_lists = {
        "X86 16-bit": (KS_ARCH_X86, KS_MODE_16),                # X86 16-bit
        "X86 32-bit": (KS_ARCH_X86, KS_MODE_32),                # X86 32-bit
        "X86 64-bit": (KS_ARCH_X86, KS_MODE_64),                # X86 64-bit
        "ARM": (KS_ARCH_ARM, KS_MODE_ARM),                      # ARM
        "ARM Thumb": (KS_ARCH_ARM, KS_MODE_THUMB),              # ARM Thumb
        "ARM64 (ARMV8)": (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN),# ARM64
        "Hexagon": (KS_ARCH_HEXAGON, KS_MODE_BIG_ENDIAN),       # Hexagon
        "Mips32": (KS_ARCH_MIPS, KS_MODE_MIPS32),               # Mips32
        "Mips64": (KS_ARCH_MIPS, KS_MODE_MIPS64),               # Mips64
        "PowerPC 32": (KS_ARCH_PPC, KS_MODE_PPC32),             # PPC32
        "PowerPC 64": (KS_ARCH_PPC, KS_MODE_PPC64),             # PPC64
        "Sparc 32": (KS_ARCH_SPARC, KS_MODE_SPARC32),           # Sparc32
        "Sparc 64": (KS_ARCH_SPARC, KS_MODE_SPARC64),           # Sparc64
        "SystemZ": (KS_ARCH_SYSTEMZ, KS_MODE_BIG_ENDIAN),       # SystemZ
    }

    endian_lists = {
        "Little Endian": KS_MODE_LITTLE_ENDIAN,                 # little endian
        "Big Endian": KS_MODE_BIG_ENDIAN,                       # big endian
    }

    syntax_lists = {
        "Intel": KS_OPT_SYNTAX_INTEL,
        "Nasm": KS_OPT_SYNTAX_NASM,
        "AT&T": KS_OPT_SYNTAX_ATT
    }

    def __init__(self, arch=None, mode=None):
        # update current arch and mode
        self.update_hardware_mode()

        # override arch & mode if provided
        if arch is not None:
            self.arch = arch
        if mode is not None:
            self.mode = mode

        # IDA uses Intel syntax by default
        self.syntax = KS_OPT_SYNTAX_INTEL

    # return Keystone arch & mode (with endianess included)
    @staticmethod
    def get_hardware_mode():
        (arch, mode) = (None, None)

        # heuristically detect hardware setup
        info = idaapi.get_inf_structure()
        
        try:
            cpuname = info.procname.lower()
        except:
            cpuname = info.procName.lower()

        try:
            # since IDA7 beta 3 (170724) renamed inf.mf -> is_be()/set_be()
            is_be = idaapi.cvar.inf.is_be()
        except:
            # older IDA versions
            is_be = idaapi.cvar.inf.mf
        # print("Keypatch BIG_ENDIAN = %s" %is_be)
        
        if cpuname == "metapc":
            arch = KS_ARCH_X86
            if info.is_64bit():
                mode = KS_MODE_64
            elif info.is_32bit():
                mode = KS_MODE_32
            else:
                mode = KS_MODE_16
        elif cpuname.startswith("arm"):
            # ARM or ARM64
            if info.is_64bit():
                arch = KS_ARCH_ARM64
                if is_be:
                    mode = KS_MODE_BIG_ENDIAN
                else:
                    mode = KS_MODE_LITTLE_ENDIAN
            else:
                arch = KS_ARCH_ARM
                # either big-endian or little-endian
                if is_be:
                    mode = KS_MODE_ARM | KS_MODE_BIG_ENDIAN
                else:
                    mode = KS_MODE_ARM | KS_MODE_LITTLE_ENDIAN
        elif cpuname.startswith("sparc"):
            arch = KS_ARCH_SPARC
            if info.is_64bit():
                mode = KS_MODE_SPARC64
            else:
                mode = KS_MODE_SPARC32
            if is_be:
                mode |= KS_MODE_BIG_ENDIAN
            else:
                mode |= KS_MODE_LITTLE_ENDIAN
        elif cpuname.startswith("ppc"):
            arch = KS_ARCH_PPC
            if info.is_64bit():
                mode = KS_MODE_PPC64
            else:
                mode = KS_MODE_PPC32
            if cpuname == "ppc":
                # do not support Little Endian mode for PPC
                mode += KS_MODE_BIG_ENDIAN
        elif cpuname.startswith("mips"):
            arch = KS_ARCH_MIPS
            if info.is_64bit():
                mode = KS_MODE_MIPS64
            else:
                mode = KS_MODE_MIPS32
            if is_be:
                mode |= KS_MODE_BIG_ENDIAN
            else:
                mode |= KS_MODE_LITTLE_ENDIAN
        elif cpuname.startswith("systemz") or cpuname.startswith("s390x"):
            arch = KS_ARCH_SYSTEMZ
            mode = KS_MODE_BIG_ENDIAN

        return (arch, mode)

    def update_hardware_mode(self):
        (self.arch, self.mode) = self.get_hardware_mode()

    # normalize assembly code
    # remove comment at the end of assembly code
    @staticmethod
    def asm_normalize(text):
        text = ' '.join(text.split())
        if text.rfind(';') != -1:
            return text[:text.rfind(';')].strip()

        return text.strip()

    @staticmethod
    # check if input address is valid
    # return
    #       -1  invalid address at target binary
    #        0  type mismatch of input address
    #        1  valid address at target binary
    def check_address(address):
        try:
            if idaapi.isEnabled(address):
                return 1
            else:
                return -1
        except:
            # invalid type
            return 0

    ### resolve IDA names from input asm code
    # todo: a better syntax parser for all archs
    def ida_resolve(self, assembly, address=idc.BADADDR):
        def _resolve(_op, ignore_kw=True):
            names = re.findall(r"[\$a-z0-9_:\.]+", _op, re.I)

            # try to resolve all names
            for name in names:
                # ignore known keywords
                if ignore_kw and name in ('byte', 'near', 'short', 'word', 'dword', 'ptr', 'offset'):
                    continue

                sym = name

                # split segment reg
                parts = name.partition(':')
                if parts[2] != '':
                    sym = parts[2]

                (type, value) = get_name_value(address, sym)

                # skip if name doesn't exist or segment / segment registers
                if type in (idaapi.NT_SEG, idaapi.NT_NONE):
                    continue

                _op = _op.replace(sym, '0x{0:X}'.format(value))

            return _op

        if self.check_address(address) == 0:
            print("Keypatch: WARNING: invalid input address {0}".format(address))
            return assembly

        # for now, we only support IDA name resolve for X86, ARM, ARM64, MIPS, PPC, SPARC
        if not (self.arch in (KS_ARCH_X86, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_PPC, KS_ARCH_SPARC)):
            return assembly

        _asm = assembly.partition(' ')
        mnem = _asm[0]
        opers = _asm[2].split(',')

        for idx, op in enumerate(opers):
            _op = list(op.partition('['))
            ignore_kw = True
            if _op[1] == '':
                _op[2] = _op[0]
                _op[0] = ''
            else:
                _op[0] = _resolve(_op[0], ignore_kw=True)
                ignore_kw = False

            _op[2] = _resolve(_op[2], ignore_kw=ignore_kw)

            opers[idx] = ''.join(_op)

        asm = "{0} {1}".format(mnem, ','.join(opers))
        return asm

    # return bytes of instruction or data
    # return None on failure
    def ida_get_item(self, address, hex_output=False):
        if self.check_address(address) != 1:
            # not a valid address
            return (None, 0)

        # return None if address is in the middle of instruction / data
        if address != idc.ItemHead(address):
            return (None, 0)

        len = idc.ItemSize(address)
        item = idaapi.get_many_bytes(address, len)

        if item is None:
            return (None, 0)

        if hex_output:
            item = to_hexstr(item)

        return (item, len)

    @staticmethod
    def get_op_dtype_name(op_idx):
        dtyp_lists = {
            idaapi.dt_byte: 'byte',     #  8 bit
            idaapi.dt_word: 'word',     #  16 bit
            idaapi.dt_dword: 'dword',   #  32 bit
            idaapi.dt_float: 'dword',   #  4 byte
            idaapi.dt_double: 'dword',  #  8 byte
            #idaapi.dt_tbyte = 5        #  variable size (ph.tbyte_size)
            #idaapi.dt_packreal = 6         #  packed real format for mc68040
            idaapi.dt_qword: 'qword',   #  64 bit
            idaapi.dt_byte16: 'xmmword',#  128 bit
            #idaapi.dt_code = 9         #  ptr to code (not used?)
            #idaapi.dt_void = 10        #  none
            #idaapi.dt_fword = 11       #  48 bit
            #idaapi.dt_bitfild = 12     #  bit field (mc680x0)
            #idaapi.dt_string = 13      #  pointer to asciiz string
            #idaapi.dt_unicode = 14     #  pointer to unicode string
            #idaapi.dt_3byte = 15       #  3-byte data
            #idaapi.dt_ldbl = 16        #  long double (which may be different from tbyte)
            idaapi.dt_byte32: 'ymmword',# 256 bit
        }

        dtype = idaapi.cmd.Operands[op_idx].dtyp
        dtyp_size = idaapi.get_dtyp_size(dtype)
        if dtype == idaapi.dt_tbyte:
            if dtyp_size == 10:
                return 'xword'

        dtyp_name = dtyp_lists.get(idaapi.cmd.Operands[op_idx].dtyp, None)

        return dtyp_name

    # return asm instructions from start to end
    def ida_get_disasm_range(self, start, end):
        codes = []
        while start < end:
            asm = self.asm_normalize(idc.GetDisasm(start))
            if asm == None:
                asm = ''
            codes.append(asm)
            start = start + idc.ItemSize(start)

        return codes

    # get disasm from IDA
    # return '' on invalid address
    def ida_get_disasm(self, address, fixup=False):

        def GetMnem(asm):
            sp = asm.find(' ')
            if sp == -1:
                return asm
            return asm[:sp]

        if self.check_address(address) != 1:
            # not a valid address
            return ''

        # return if address is in the middle of instruction / data
        if address != idc.ItemHead(address):
            return ''

        asm = self.asm_normalize(idc.GetDisasm(address))
        # for now, only support IDA syntax fixup for Intel CPU
        if not fixup or self.arch != KS_ARCH_X86:
            return asm

        # KS_ARCH_X86 mode
        # rebuild disasm code from IDA
        i = 0
        mnem = GetMnem(asm)
        if mnem == '' or mnem in ('rep', 'repne', 'repe'):
            return asm

        opers = []
        while GetOpType(address, i) > 0 and i < 6:
            t = GetOpType(address, i)
            o = GetOpnd(address, i)

            if t in (idc.o_mem, idc.o_displ):
                parts = list(o.partition(':'))
                if parts[2] == '':
                    parts[2] = parts[0]
                    parts[0] = ''

                if '[' not in parts[2]:
                    parts[2] = '[{0}]'.format(parts[2])

                o = ''.join(parts)

                if 'ptr ' not in o:
                    dtyp_name = self.get_op_dtype_name(i)
                    if dtyp_name != None:
                        o = "{0} ptr {1}".format(dtyp_name, o)

            opers.append(o)
            i += 1

        asm = mnem
        for o in opers:
            if o != '':
                asm = "{0} {1},".format(asm, o)

        asm = asm.strip(',')
        return asm

    # assemble code with Keystone
    # return (encoding, count), or (None, 0) on failure
    def assemble(self, assembly, address, arch=None, mode=None, syntax=None):

        # return assembly with arithmetic equation evaluated
        def eval_operand(assembly, start, stop, prefix=''):
            imm = assembly[start+1:stop]
            try:
                eval_imm = eval(imm)
                if eval_imm > 0x80000000:
                    eval_imm = 0xffffffff - eval_imm
                    eval_imm += 1
                    eval_imm = -eval_imm
                return assembly.replace(prefix + imm, prefix + hex(eval_imm))
            except:
                return assembly

        # IDA uses different syntax from Keystone
        # sometimes, we can convert code to be consumable by Keystone
        def fix_ida_syntax(assembly):

            # return True if this insn needs to be fixed
            def check_arm_arm64_insn(arch, mnem):
                if arch == KS_ARCH_ARM:
                    if mnem.startswith("ldr") or mnem.startswith("str"):
                        return True
                    return False
                elif arch == KS_ARCH_ARM64:
                    if mnem.startswith("ldr") or mnem.startswith("str"):
                        return True
                    return mnem in ("stp")
                return False

            # return True if this insn needs to be fixed
            def check_ppc_insn(mnem):
                return mnem in ("stw")

            # replace the right most string occurred
            def rreplace(s, old, new):
                li = s.rsplit(old, 1)
                return new.join(li)

            # convert some ARM pre-UAL assembly to UAL, so Keystone can handle it
            # example: streqb --> strbeq
            def fix_arm_ual(mnem, assembly):
                # TODO: this is not an exhaustive list yet
                if len(mnem) != 6:
                    return assembly

                if (mnem[-1] in ('s', 'b', 'h', 'd')):
                    #print(">> 222", mnem[3:5])
                    if mnem[3:5] in ("cc", "eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "al"):
                        return assembly.replace(mnem, mnem[:3] + mnem[-1] + mnem[3:5], 1)

                return assembly

            if self.arch != KS_ARCH_X86:
                assembly = assembly.lower()
            else:
                # Keystone does not support immediate 0bh, but only 0Bh
                assembly = assembly.upper()

            # however, 0X must be converted to 0x
            # Keystone should fix this limitation in the future
            assembly = assembly.replace("0X", " 0x")

            _asm = assembly.partition(' ')
            mnem = _asm[0]
            if mnem == '':
                return assembly

            # for PPC, Keystone does not accept registers with 'r' prefix,
            # but only the number behind. lets try to fix that here by
            # removing the prefix 'r'.
            if self.arch == KS_ARCH_PPC:
                for n in range(32):
                    r = " r%u," %n
                    if r in assembly:
                        assembly = assembly.replace(r, " %u," %n)
                for n in range(32):
                    r = "(r%u)" %n
                    if r in assembly:
                        assembly = assembly.replace(r, "(%u)" %n)
                for n in range(32):
                    r = ", r%u" %n
                    if assembly.endswith(r):
                        assembly = rreplace(assembly, r, ", %u" %n)

            if self.arch == KS_ARCH_X86:
                if mnem == "RETN":
                    # replace retn with ret
                    return assembly.replace('RETN', 'RET', 1)
                if 'OFFSET ' in assembly:
                    return assembly.replace('OFFSET ', ' ')
                if mnem in ('CALL', 'JMP') or mnem.startswith('LOOP'):
                    # remove 'NEAR PTR'
                    if ' NEAR PTR ' in assembly:
                        return assembly.replace(' NEAR PTR ', ' ')
                elif mnem[0] == 'J':
                    # JMP instruction
                    if ' SHORT ' in assembly:
                        # remove ' short '
                        return assembly.replace(' SHORT ', ' ')
            elif self.arch in (KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_PPC):
                # *** ARM
                # LDR     R1, [SP+rtld_fini],#4
                # STR     R2, [SP,#-4+rtld_fini]!
                # STR     R0, [SP,#fini]!
                # STR     R12, [SP,#4+var_8]!

                # *** ARM64
                # STP     X29, X30, [SP,#-0x10+var_150]!
                # STR     W0, [X29,#0x150+var_8]
                # LDR     X0, [X0,#(qword_4D6678 - 0x4D6660)]
                # TODO:
                # ADRP    X19, #interactive@PAGE

                # *** PPC
                # stw     r5, 0x120+var_108(r1)
                
                if self.arch == KS_ARCH_ARM and mode == KS_MODE_THUMB:
                    assembly = assembly.replace('movt.w', 'movt')

                if self.arch == KS_ARCH_ARM:
                    #print(">> before UAL fix: ", assembly)
                    assembly = fix_arm_ual(mnem, assembly)
                    #print(">> after UAL fix: ", assembly)

                if check_arm_arm64_insn(self.arch, mnem) or (("[" in assembly) and ("]" in assembly)):
                    bang = assembly.find('#')
                    bracket = assembly.find(']')
                    if bang != -1 and bracket != -1 and bang < bracket:
                        return eval_operand(assembly, bang, bracket, '#')
                    elif '+0x0]' in assembly:
                        return assembly.replace('+0x0]', ']')
                elif check_ppc_insn(mnem):
                    start = assembly.find(', ')
                    stop = assembly.find('(')
                    if start != -1 and stop != -1 and start < stop:
                        return eval_operand(assembly, start, stop)
            return assembly

        def is_thumb(address):
            return idc.GetReg(address, 'T') == 1

        if self.check_address(address) == 0:
            return (None, 0)

        # use default syntax, arch and mode if not provided
        if syntax is None:
            syntax = self.syntax
        if arch is None:
            arch = self.arch
        if mode is None:
            mode = self.mode

        if arch == KS_ARCH_ARM and is_thumb(address):
            mode = KS_MODE_THUMB

        try:
            ks = Ks(arch, mode)
            if arch == KS_ARCH_X86:
                ks.syntax = syntax
            encoding, count = ks.asm(fix_ida_syntax(assembly), address)
        except KsError as e:
            # keep the below code for debugging
            #print("Keypatch Error: {0}".format(e))
            #print("Original asm: {0}".format(assembly))
            #print("Fixed up asm: {0}".format(fix_ida_syntax(assembly)))
            encoding, count = None, 0

        return (encoding, count)


    # patch at address, return the number of written bytes & original data
    # this process can fail in some cases
    @staticmethod
    def patch_raw(address, patch_data, len):
        ea = address
        orig_data = ''

        while ea < (address + len):

            if not idc.hasValue(idc.GetFlags(ea)):
                print("Keypatch: FAILED to read data at 0x{0:X}".format(ea))
                break

            orig_byte = idc.Byte(ea)
            orig_data += chr(orig_byte)
            patch_byte = ord(patch_data[ea - address])

            if patch_byte != orig_byte:
                # patch one byte
                if idaapi.patch_byte(ea, patch_byte) != 1:
                    print("Keypatch: FAILED to patch byte at 0x{0:X} [0x{1:X}]".format(ea, patch_byte))
                    break
            ea += 1
        return (ea - address, orig_data)

    # patch at address, return the number of written bytes & original data
    # on patch failure, we revert to the original code, then return (None, None)
    def patch(self, address, patch_data, len):
        # save original function end to fix IDA re-analyze issue after patching
        orig_func_end = idc.GetFunctionAttr(address, idc.FUNCATTR_END)

        (patched_len, orig_data) = self.patch_raw(address, patch_data, len)

        if len != patched_len:
            # patch failure
            if patched_len > 0:
                # revert the changes
                (rlen, _) = self.patch_raw(address, orig_data, patched_len)
                if rlen == patched_len:
                    print("Keypatch: successfully reverted changes of {0:d} byte(s) at 0x{1:X} [{2}]".format(
                                        patched_len, address, to_hexstr(orig_data)))
                else:
                    print("Keypatch: FAILED to revert changes of {0:d} byte(s) at 0x{1:X} [{2}]".format(
                                        patched_len, address, to_hexstr(orig_data)))

            return (None, None)

        # ask IDA to re-analyze the patched area
        if orig_func_end == idc.BADADDR:
            # only analyze patched bytes, otherwise it would take a lot of time to re-analyze the whole binary
            idaapi.analyze_area(address, address + patched_len + 1)
        else:
            idaapi.analyze_area(address, orig_func_end)

            # try to fix IDA function re-analyze issue after patching
            idaapi.func_setend(address, orig_func_end)

        return (patched_len, orig_data)

    # return number of bytes patched
    # return
    #    0  Invalid assembly
    #   -1  PatchByte failure
    #   -2  Can't read original data
    #   -3  Invalid address
    def patch_code(self, address, assembly, syntax, padding, save_origcode, orig_asm=None, patch_data=None, patch_comment=None, undo=False):
        global patch_info

        if self.check_address(address) != 1:
            # not a valid address
            return -3

        orig_comment = idc.Comment(address)
        if orig_comment is None:
            orig_comment = ''

        nop_comment = ""
        padding_len = 0
        if not undo:
            # we are patching via Patcher
            (orig_encoding, orig_len) = self.ida_get_item(address)
            if (orig_encoding, orig_len) == (None, 0):
                return -2

            (encoding, count) = self.assemble(assembly, address, syntax=syntax)
            if encoding is None:
                return 0

            patch_len = len(encoding)
            patch_data = ''.join(chr(c) for c in encoding)

            if patch_data == orig_encoding:
                #print("Keypatch: no need to patch, same encoding data [{0}] at 0x{1:X}".format(to_hexstr(orig_encoding), address))
                return orig_len

            # for now, only support NOP padding on Intel CPU
            if padding and self.arch == KS_ARCH_X86:
                if patch_len < orig_len:
                    padding_len = orig_len - patch_len
                    patch_len = orig_len
                    patch_data = patch_data.ljust(patch_len, X86_NOP)
                elif patch_len > orig_len:
                    patch_end = address + patch_len - 1
                    ins_end = ItemEnd(patch_end)
                    padding_len = ins_end - patch_end - 1

                    if padding_len > 0:
                        patch_len = ins_end - address
                        patch_data = patch_data.ljust(patch_len, X86_NOP)

                if padding_len > 0:
                    nop_comment = "\nKeypatch padded NOP to next boundary: {0} bytes".format(padding_len)

            orig_asm = self.ida_get_disasm_range(address, address + patch_len)
        else:
            # we are reverting the change via "Undo" menu
            patch_len = len(patch_data)

        (plen, p_orig_data) = self.patch(address, patch_data, patch_len)
        if plen is None:
            # failed to patch
            return -1

        if not undo: # we are patching
            new_patch_comment = None
            if save_origcode is True:
                # append original instruction to comments
                if orig_comment == '':
                    new_patch_comment = "Keypatch modified this from:\n  {0}{1}".format('\n  '.join(orig_asm), nop_comment)
                else:
                    new_patch_comment = "\nKeypatch modified this from:\n  {0}{1}".format('\n  '.join(orig_asm), nop_comment)

                new_comment = "{0}{1}".format(orig_comment, new_patch_comment)
                idc.MakeComm(address, new_comment)

            if padding_len == 0:
                print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen,
                                        address, to_hexstr(p_orig_data), to_hexstr(patch_data)))
            else:
                print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}], with {4} byte(s) NOP padded".format(plen,
                                        address, to_hexstr(p_orig_data), to_hexstr(patch_data), padding_len))
            # save this patching for future "undo"
            patch_info.append((address, assembly, p_orig_data, new_patch_comment))
        else:   # we are reverting
            if patch_comment:
                # clean previous IDA comment by replacing it with ''
                new_comment = orig_comment.replace(patch_comment, '')
                idc.MakeComm(address, new_comment)

            print("Keypatch: successfully reverted {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen,
                                        address, to_hexstr(p_orig_data), to_hexstr(patch_data)))

        return plen

    # fill a range of code [addr_begin, addr_end].
    # return the length of patched area
    # on failure, return 0 = wrong input, -1 = failed to patch
    def fill_code(self, addr_begin, addr_end, assembly, syntax, padding, save_origcode, orig_asm=None):
        # treat input as assembly code first
        (encoding, _) =  self.assemble(assembly, addr_begin, syntax=syntax)

        if encoding is None:
            # input might be a hexcode string. try to convert it to raw bytes
            encoding = convert_hexstr(assembly)

        if encoding is None:
            # invalid input: this is neither assembly nor hexcode string
            return 0

        # save original assembly code before overwritting them
        orig_asm = self.ida_get_disasm_range(addr_begin, addr_end)

        # save original comment at addr_begin
        # TODO: save comments in this range, but how to interleave them?
        orig_comment = idc.Comment(addr_begin)
        if orig_comment is None:
            orig_comment = ''

        patch_data = ""
        assembly_new = []
        size = addr_end - addr_begin
        # calculate filling data
        encode_chr = ''.join(chr(c) for c in encoding)
        while True:
            if len(patch_data) + len(encode_chr) <= size:
                patch_data = patch_data + encode_chr
                assembly_new += [assembly.strip()]
            else:
                break

        # for now, only support NOP padding on Intel CPU
        if padding and self.arch == KS_ARCH_X86:
            for i in range(size -len(patch_data)):
                assembly_new += ["nop"]
            patch_data = patch_data.ljust(size, X86_NOP)

        (plen, p_orig_data) = self.patch(addr_begin, patch_data, len(patch_data))
        if plen is None:
            # failed to patch
            return -1

        new_patch_comment = ''
        # append original instruction to comments
        if save_origcode is True:
            if orig_comment == '':
                new_patch_comment = "Keypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n  {3}".format(addr_begin, addr_end - 1, addr_end - addr_begin, '\n  '.join(orig_asm))
            else:
                new_patch_comment = "\nKeypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n  {3}".format(addr_begin, addr_end - 1, addr_end - addr_begin, '\n  '.join(orig_asm))

            new_comment = "{0}{1}".format(orig_comment, new_patch_comment)
            idc.MakeComm(addr_begin, new_comment)

        print("Keypatch: successfully filled range [0x{0:X}:0x{1:X}] ({2} bytes) with \"{3}\", replaced \"{4}\"".format(
                    addr_begin, addr_end - 1, addr_end - addr_begin, assembly, '; '.join(orig_asm)))

        # save this modification for future "undo"
        patch_info.append((addr_begin, '\n  '.join(assembly_new), p_orig_data, new_patch_comment))

        return plen


    ### Form helper functions
    @staticmethod
    def dict_to_ordered_list(dictionary):
        list = sorted(dictionary.items(), key=lambda t: t[0], reverse=False)
        keys = [i[0] for i in list]
        values = [i[1] for i in list]

        return (keys, values)

    def get_value_by_idx(self, dictionary, idx, default=None):
        (keys, values) = self.dict_to_ordered_list(dictionary)

        try:
            val = values[idx]
        except IndexError:
            val = default

        return val

    def find_idx_by_value(self, dictionary, value, default=None):
        (keys, values) = self.dict_to_ordered_list(dictionary)

        try:
            idx = values.index(value)
        except:
            idx = default

        return idx

    def get_arch_by_idx(self, idx):
        return self.get_value_by_idx(self.arch_lists, idx)

    def find_arch_idx(self, arch, mode):
        return self.find_idx_by_value(self.arch_lists, (arch, mode))

    def get_syntax_by_idx(self, idx):
        return self.get_value_by_idx(self.syntax_lists, idx, self.syntax)

    def find_syntax_idx(self, syntax):
        return self.find_idx_by_value(self.syntax_lists, syntax)
    ### /Form helper functions


# Common ancestor form to be derived by Patcher, FillRange & Search
class Keypatch_Form(idaapi.Form):
    # prepare for form initializing
    def setup(self, kp_asm, address, assembly=None):
        self.kp_asm = kp_asm
        self.address = address

        # update ordered list of arch and syntax
        self.syntax_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.syntax_lists)[0]
        self.arch_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.arch_lists)[0]

        # update current arch & mode
        self.kp_asm.update_hardware_mode()

        # find right value for c_arch & c_endian controls
        mode = self.kp_asm.mode
        self.endian_id = 0   # little endian
        if self.kp_asm.mode & KS_MODE_BIG_ENDIAN:
            self.endian_id = 1   # big endian
            mode = self.kp_asm.mode - KS_MODE_BIG_ENDIAN

        self.arch_id = self.kp_asm.find_arch_idx(self.kp_asm.arch, mode)

        self.syntax_id = 0  # to make non-X86 arch happy
        if self.kp_asm.arch == KS_ARCH_X86:
            self.syntax_id = self.kp_asm.find_syntax_idx(self.kp_asm.syntax)

        # get original instruction and bytes
        self.orig_asm = kp_asm.ida_get_disasm(address)
        (self.orig_encoding, self.orig_len) = kp_asm.ida_get_item(address, hex_output=True)
        if self.orig_encoding is None:
            self.orig_encoding = ''

        if assembly is None:
            self.asm = self.kp_asm.ida_get_disasm(self.address, fixup=True)
        else:
            self.asm = assembly

    # update Encoding control
    # return True on success, False on failure
    def _update_encoding(self, arch, mode):
        try:
            syntax = None
            if arch == KS_ARCH_X86:
                syntax_id = self.GetControlValue(self.c_syntax)
                syntax = self.kp_asm.get_syntax_by_idx(syntax_id)

            address = self.GetControlValue(self.c_addr)
            try:
                idaapi.isEnabled(address)
            except:
                # invalid address value
                address = 0

            assembly = self.GetControlValue(self.c_assembly)
            raw_assembly = self.kp_asm.ida_resolve(assembly, address)
            self.SetControlValue(self.c_raw_assembly, raw_assembly)

            (encoding, count) =  self.kp_asm.assemble(raw_assembly, address, arch=arch,
                                                    mode=mode, syntax=syntax)

            if encoding is None:
                self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT)
                self.SetControlValue(self.c_encoding_len, 0)
                return False
            else:
                text = ""
                for byte in encoding:
                    text += "%02X " % byte
                text.strip()
                if text == "":
                    # error?
                    self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT)
                    return False
                else:
                    self.SetControlValue(self.c_encoding, text.strip())
                    self.SetControlValue(self.c_encoding_len, len(encoding))
                    return True
        except Exception,e:
            print (str(e))
            import traceback
            traceback.print_exc()
            self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT)
            return False

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        return 1

    # update Patcher & Fillrange controls
    def update_patchform(self, fid):
        self.EnableField(self.c_endian, False)
        self.EnableField(self.c_addr, False)

        (arch, mode) = (self.kp_asm.arch, self.kp_asm.mode)
        # assembly is focused
        self.SetFocusedField(self.c_assembly)

        if arch == KS_ARCH_X86:
            # do not show Endian control
            self.ShowField(self.c_endian, False)
            # allow to choose Syntax
            self.ShowField(self.c_syntax, True)
            self.ShowField(self.c_opt_padding, True)
        else:   # do not show Syntax control for non-X86 mode
            self.ShowField(self.c_syntax, False)
            # for now, we do not support padding for non-X86 archs
            self.ShowField(self.c_opt_padding, False)
            #self.EnableField(self.c_opt_padding, False)

        # update other controls & Encoding with live assembling
        self.update_controls(arch, mode)

        return 1

    # update some controls - including Encoding control
    def update_controls(self, arch, mode):
        # Fixup & Encoding-len are read-only controls
        self.EnableField(self.c_raw_assembly, False)
        self.EnableField(self.c_encoding_len, False)

        # Encoding is enable to allow user to select & copy
        self.EnableField(self.c_encoding, True)

        if self.GetControlValue(self.c_endian) == 1:
            endian = KS_MODE_BIG_ENDIAN
        else:
            endian = KS_MODE_LITTLE_ENDIAN

        # update encoding with live assembling
        self._update_encoding(arch, mode | endian)

        return 1

    # get Patcher/FillRange options
    def get_opts(self, name=None):
        names = self.c_opt_chk.children_names
        val = self.c_opt_chk.value
        opts = {}
        for i in range(len(names)):
            opts[names[i]] = val & (2**i)

        if name != None:
            opts[name] = val

        return opts


# Fill Range form
class Keypatch_FillRange(Keypatch_Form):
    def __init__(self, kp_asm, addr_begin, addr_end, assembly=None, opts=None):
        self.setup(kp_asm, addr_begin, assembly)
        self.addr_end = addr_end

        # create FillRange form
        super(Keypatch_FillRange, self).__init__(
            r"""STARTITEM {id:c_assembly}
BUTTON YES* Patch
KEYPATCH:: Fill Range

            {FormChangeCb}
            <Endian     :{c_endian}>
            <~S~yntax     :{c_syntax}>
            <Start      :{c_addr}>
            <End        :{c_addr_end}>
            <Size       :{c_size}>
            <~A~ssembly   :{c_assembly}>
             <##-   Fixup :{c_raw_assembly}>
             <##-   Encode:{c_encoding}>
             <##-   Size  :{c_encoding_len}>
            <~N~OPs padding until next instruction boundary:{c_opt_padding}>
            <Save ~o~riginal instructions in IDA comment:{c_opt_comment}>{c_opt_chk}>
            """, {
            'c_endian': self.DropdownListControl(
                          items = self.kp_asm.endian_lists.keys(),
                          readonly = True,
                          selval = self.endian_id),
            'c_addr': self.NumericInput(value=addr_begin, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_addr_end': self.NumericInput(value=addr_end - 1, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_size': self.NumericInput(value=addr_end - addr_begin, swidth=8, tp=self.FT_DEC),
            'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN),
            'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN),
            'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC),
            'c_syntax': self.DropdownListControl(
                          items = self.syntax_keys,
                          readonly = True,
                          selval = self.syntax_id),
            'c_opt_chk':idaapi.Form.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']),
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        # make some controls read-only in FillRange mode
        self.EnableField(self.c_size, False)
        self.EnableField(self.c_addr_end, False)

        return self.update_patchform(fid)


# Patcher form
class Keypatch_Patcher(Keypatch_Form):
    def __init__(self, kp_asm, address, assembly=None, opts=None):
        self.setup(kp_asm, address, assembly)

        # create Patcher form
        super(Keypatch_Patcher, self).__init__(
            r"""STARTITEM {id:c_assembly}
BUTTON YES* Patch
KEYPATCH:: Patcher

            {FormChangeCb}
            <Endian     :{c_endian}>
            <~S~yntax     :{c_syntax}>
            <Address    :{c_addr}>
            <Original   :{c_orig_assembly}>
             <##-   Encode:{c_orig_encoding}>
             <##-   Size  :{c_orig_len}>
            <~A~ssembly   :{c_assembly}>
             <##-   Fixup :{c_raw_assembly}>
             <##-   Encode:{c_encoding}>
             <##-   Size  :{c_encoding_len}>
            <~N~OPs padding until next instruction boundary:{c_opt_padding}>
            <Save ~o~riginal instructions in IDA comment:{c_opt_comment}>{c_opt_chk}>
            """, {
            'c_endian': self.DropdownListControl(
                          items = self.kp_asm.endian_lists.keys(),
                          readonly = True,
                          selval = self.endian_id),
            'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_orig_assembly': self.StringInput(value=self.orig_asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_orig_encoding': self.StringInput(value=self.orig_encoding[:MAX_ENCODING_LEN], width=MAX_ENCODING_LEN),
            'c_orig_len': self.NumericInput(value=self.orig_len, swidth=8, tp=self.FT_DEC),
            'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN),
            'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN),
            'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC),
            'c_syntax': self.DropdownListControl(
                          items = self.syntax_keys,
                          readonly = True,
                          selval = self.syntax_id),
            'c_opt_chk':self.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']),
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        # make some fields read-only in Patch mode
        self.EnableField(self.c_orig_assembly, False)
        self.EnableField(self.c_orig_encoding, False)
        self.EnableField(self.c_orig_len, False)

        return self.update_patchform(fid)


# Search position chooser
class SearchResultChooser(idaapi.Choose2):
    def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False):
        super(SearchResultChooser, self).__init__(
            title,
            [["Address", idaapi.Choose2.CHCOL_HEX|40]],
            flags = flags,
            width = width,
            height = height,
            embedded = embedded)
        self.n = 0
        self.items = items
        self.selcount = 0
        self.modal = modal

    def OnClose(self):
        return

    def OnSelectLine(self, n):
        self.selcount += 1
        idc.Jump(self.items[n][0])

    def OnGetLine(self, n):
        res = self.items[n]
        res = [idc.atoa(res[0])]
        return res

    def OnGetSize(self):
        n = len(self.items)
        return n

    def show(self):
        return self.Show(self.modal) >= 0


# Search form
class Keypatch_Search(Keypatch_Form):
    def __init__(self, kp_asm, address, assembly=None):
        self.setup(kp_asm, address, assembly)

        # create Search form
        super(Keypatch_Search, self).__init__(
            r"""STARTITEM {id:c_assembly}
BUTTON YES* Search
KEYPATCH:: Search

            {FormChangeCb}
            <A~r~ch       :{c_arch}>
            <E~n~dian     :{c_endian}>
            <~S~yntax     :{c_syntax}>
            <A~d~dress    :{c_addr}>
            <~A~ssembly   :{c_assembly}>
             <##-   Fixup :{c_raw_assembly}>
             <##-   Encode:{c_encoding}>
             <##-   Size  :{c_encoding_len}>
            """, {
            'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN),
            'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN),
            'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC),
            'c_arch': self.DropdownListControl(
                          items = self.arch_keys,
                          readonly = True,
                          selval = self.arch_id,
                          width = 32),
            'c_endian': self.DropdownListControl(
                          items = self.kp_asm.endian_lists.keys(),
                          readonly = True,
                          selval = self.endian_id),
            'c_syntax': self.DropdownListControl(
                          items = self.syntax_keys,
                          readonly = True,
                          selval = self.syntax_id),
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        # handle the search button
        if fid == -2:
            address = 0
            addresses = []
            while address != idc.BADADDR:
                address = idc.FindBinary(address, idc.SEARCH_DOWN, self.GetControlValue(self.c_encoding))
                if address == idc.BADADDR:
                    break
                addresses.append([address])
                address = address + 1
            c = SearchResultChooser("Searching for [{0}]".format(self.GetControlValue(self.c_raw_assembly)), addresses)
            c.show()
            return 1

        # only Search mode allows to select arch+mode
        arch_id = self.GetControlValue(self.c_arch)
        (arch, mode) = self.kp_asm.get_arch_by_idx(arch_id)

        # assembly is focused
        self.SetFocusedField(self.c_assembly)

        if arch == KS_ARCH_X86:
            # enable Syntax and disable Endian for x86
            self.ShowField(self.c_syntax, True)
            self.EnableField(self.c_syntax, True)
            self.syntax_id = self.GetControlValue(self.c_syntax)
            self.EnableField(self.c_endian, False)
            # set Endian index properly
            self.SetControlValue(self.c_endian, 0)
        elif arch in (KS_ARCH_ARM64, KS_ARCH_HEXAGON, KS_ARCH_SYSTEMZ):
            # no Syntax & Endian option for these archs
            self.ShowField(self.c_syntax, False)
            self.EnableField(self.c_syntax, False)
            self.EnableField(self.c_endian, False)
            # set Endian index properly
            self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0))
        elif (arch == KS_ARCH_PPC) and (mode & KS_MODE_PPC32 != 0):
            # no Syntax & Endian option for these archs
            self.ShowField(self.c_syntax, False)
            self.EnableField(self.c_syntax, False)
            self.EnableField(self.c_endian, False)
            # set Endian index properly
            self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0))
        else:
            # no Syntax & Endian option
            self.ShowField(self.c_syntax, False)
            self.EnableField(self.c_syntax, False)
            self.EnableField(self.c_endian, True)

        if self.GetControlValue(self.c_endian) == 1:
            endian = KS_MODE_BIG_ENDIAN
        else:
            endian = KS_MODE_LITTLE_ENDIAN

        # update other controls & Encoding with live assembling
        self.update_controls(arch, mode)

        return 1


# About form
class About_Form(idaapi.Form):
    def __init__(self, version):
        # create About form
        super(About_Form, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Keypatch Website
KEYPATCH:: About

            {FormChangeCb}
            Keypatch IDA plugin v%s, using Keystone Engine v%s.
            (c) Nguyen Anh Quynh + Thanh Nguyen, 2018.

            Keypatch is released under the GPL v2.
            Find more info at http://www.keystone-engine.org/keypatch
            """ %(version, keystone.__version__), {
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        if fid == -2:   # Goto homepage
            import webbrowser
            # open Keypatch homepage in a new tab, if possible
            webbrowser.open(KP_HOMEPAGE, new = 2)

        return 1


# Check-for-update form
class Update_Form(idaapi.Form):
    def __init__(self, version, message):
        # create Update form
        super(Update_Form, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Keypatch Website
KEYPATCH:: Check for update

            {FormChangeCb}
            Your Keypatch is v%s
            %s
            """ %(version, message), {
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })
        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        if fid == -2:   # Goto homepage
            import webbrowser
            # open Keypatch homepage in a new tab, if possible
            webbrowser.open(KP_HOMEPAGE, new = 2)

        return 1


try:
    # adapted from pull request #7 by @quangnh89
    class Kp_Menu_Context(idaapi.action_handler_t):

        @classmethod
        def get_name(self):
            return self.__name__

        @classmethod
        def get_label(self):
            return self.label

        @classmethod
        def register(self, plugin, label):
            self.plugin = plugin
            self.label = label
            instance = self()
            return idaapi.register_action(idaapi.action_desc_t(
                self.get_name(),  # Name. Acts as an ID. Must be unique.
                instance.get_label(),  # Label. That's what users see.
                instance  # Handler. Called when activated, and for updating
            ))

        @classmethod
        def unregister(self):
            """Unregister the action.
            After unregistering the class cannot be used.
            """
            idaapi.unregister_action(self.get_name())

        @classmethod
        def activate(self, ctx):
            # dummy method
            return 1

        @classmethod
        def update(self, ctx):
            try:
                if ctx.form_type == idaapi.BWN_DISASM:
                    return idaapi.AST_ENABLE_FOR_FORM
                else:
                    return idaapi.AST_DISABLE_FOR_FORM
            except:
                # Add exception for main menu on >= IDA 7.0
                return idaapi.AST_ENABLE_ALWAYS
            
    # context menu for Patcher
    class Kp_MC_Patcher(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.patcher()
            return 1

    # context menu for Fill Range
    class Kp_MC_Fill_Range(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.fill_range()
            return 1

    # context menu for Undo
    class Kp_MC_Undo(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.undo()
            return 1

    # context menu for Search
    class Kp_MC_Search(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.search()
            return 1

    # context menu for Check Update
    class Kp_MC_Updater(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.updater()
            return 1

    # context menu for About
    class Kp_MC_About(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.about()
            return 1
except:
    pass


# hooks for popup menu
class Hooks(idaapi.UI_Hooks):
    if idaapi.IDA_SDK_VERSION >= 700:
        # IDA >= 700 right click widget popup
        def finish_populating_widget_popup(self, form, popup):
            if idaapi.get_widget_type(form) == idaapi.BWN_DISASM:
                try:
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/')
                except:
                    pass
    else:
        # IDA < 700 right click popup
        def finish_populating_tform_popup(self, form, popup):
            # We'll add our action to all "IDA View-*"s.
            # If we wanted to add it only to "IDA View-A", we could
            # also discriminate on the widget's title:
            #
            #  if idaapi.get_tform_title(form) == "IDA View-A":
            #      ...
            #
            if idaapi.get_tform_type(form) == idaapi.BWN_DISASM:
                try:
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/')
                except:
                    pass


# check if we already initialized Keypatch
kp_initialized = False

#--------------------------------------------------------------------------
# Plugin
#--------------------------------------------------------------------------
class Keypatch_Plugin_t(idaapi.plugin_t):
    comment = "Keypatch plugin for IDA Pro (using Keystone framework)"
    help = "Find more information on Keypatch at http://keystone-engine.org/keypatch"
    wanted_name = "Keypatch Patcher"
    wanted_hotkey = "Ctrl-Alt-K"
    flags = idaapi.PLUGIN_KEEP

    def load_configuration(self):
        # default
        self.opts = {}

        # load configuration from file
        try:
            f = open(KP_CFGFILE, "rt")
            self.opts = json.load(f)
            f.close()
        except IOError:
            print("Keypatch: FAILED to load config file. Use default setup now.")
        except Exception as e:
            print("Keypatch: FAILED to load config file, with exception: {0}".format(str(e)))

        # use default values if not defined in config file
        if 'c_opt_padding' not in self.opts:
            self.opts['c_opt_padding'] = 1

        if 'c_opt_comment' not in self.opts:
            self.opts['c_opt_comment'] = 2

        self.opts['c_opt_chk'] = self.opts['c_opt_padding'] | self.opts['c_opt_comment']

    def init(self):
        global kp_initialized

        # register popup menu handlers
        try:
            Kp_MC_Patcher.register(self, "Patcher    (Ctrl-Alt-K)")
            Kp_MC_Fill_Range.register(self, "Fill Range")
            Kp_MC_Undo.register(self, "Undo last patching")
            Kp_MC_Search.register(self, "Search")
            Kp_MC_Updater.register(self, "Check for update")
            Kp_MC_About.register(self, "About")
        except:
            pass

        # setup popup menu
        self.hooks = Hooks()
        self.hooks.hook()

        self.opts = None
        if kp_initialized == False:
            kp_initialized = True

            if idaapi.IDA_SDK_VERSION >= 700:
                # Add menu IDA >= 7.0
                idaapi.attach_action_to_menu("Edit/Keypatch/Patcher", Kp_MC_Patcher.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/About", Kp_MC_About.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Check for update", Kp_MC_Updater.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Search", Kp_MC_Search.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Undo last patching", Kp_MC_Undo.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Fill Range", Kp_MC_Fill_Range.get_name(), idaapi.SETMENU_APP)
            else:
                # add Keypatch menu
                menu = idaapi.add_menu_item("Edit/Keypatch/", "Patcher     (Ctrl-Alt-K)", "", 1, self.patcher, None)
                if menu is not None:
                    idaapi.add_menu_item("Edit/Keypatch/", "About", "", 1, self.about, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Check for update", "", 1, self.updater, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Search", "", 1, self.search, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Undo last patching", "", 1, self.undo, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Fill Range", "", 1, self.fill_range, None)
                elif idaapi.IDA_SDK_VERSION < 680:
                    # older IDAPython (such as in IDAPro 6.6) does add new submenu.
                    # in this case, put Keypatch menu in menu Edit \ Patch program
                    # not sure about v6.7, so to be safe we just check against v6.8
                    idaapi.add_menu_item("Edit/Patch program/", "-", "", 0, self.menu_null, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: About", "", 0, self.about, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Check for update", "", 0, self.updater, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Search", "", 0, self.search, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Undo last patching", "", 0, self.undo, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Fill Range", "", 0, self.fill_range, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Patcher     (Ctrl-Alt-K)", "", 0, self.patcher, None)

            print("=" * 80)
            print("Keypatch v{0} (c) Nguyen Anh Quynh & Thanh Nguyen, 2018".format(VERSION))
            print("Keypatch is using Keystone v{0}".format(keystone.__version__))
            print("Keypatch Patcher's shortcut key is Ctrl-Alt-K")
            print("Use the same hotkey Ctrl-Alt-K to open 'Fill Range' window on a selected range of code")
            print("To revert (undo) the last patching, choose menu Edit | Keypatch | Undo last patching")
            print("Keypatch Search is available from menu Edit | Keypatch | Search")
            print("Find more information about Keypatch at http://keystone-engine.org/keypatch")

            self.load_configuration()

            print("=" * 80)
            self.kp_asm = Keypatch_Asm()

        return idaapi.PLUGIN_KEEP

    def term(self):
        if self.hooks is not None:
            self.hooks.unhook()
            self.hooks = None

        if self.opts is None:
            return
        #save configuration to file
        try:
            json.dump(self.opts, open(KP_CFGFILE, "wt"))
        except Exception as e:
            print("Keypatch: FAILED to save config file, with exception: {0}".format(str(e)))
        else:
            print("Keypatch: configuration is saved to {0}".format(KP_CFGFILE))

    # null handler
    def menu_null(self):
        pass

    # handler for About menu
    def about(self):
        f = About_Form(VERSION)
        f.Execute()
        f.Free()

    # handler for Check-for-Update menu
    def updater(self):
        (r, content) = url_download(KP_GITHUB_VERSION)
        if r == 0:
            # find stable version
            sig = 'VERSION_STABLE = "'
            tmp = content[content.find(sig)+len(sig):]
            version_stable = tmp[:tmp.find('"')]

            # compare with the current version
            if version_stable == VERSION:
                f = Update_Form(VERSION, "Good, you are already on the latest stable version!")
                f.Execute()
                # free this form
                f.Free()
            else:
                f = Update_Form(VERSION, "Download latest stable version {0} from http://keystone-engine.org/keypatch".format(version_stable))
                f.Execute()
                # free this form
                f.Free()
        else:
            # fail to download
            idc.Warning("ERROR: Keypatch failed to connect to internet (Github). Try again later.")
            print("Keypatch: FAILED to connect to Github to check for latest update. Try again later.")

    # handler for Undo menu
    def undo(self):
        global patch_info
        if len(patch_info) == 0:
            # TODO: disable Undo menu?
            idc.Warning("ERROR: Keypatch already got to the last undo patching!")
        else:
            (address, assembly, p_orig_data, patch_comment) = patch_info[-1]

            # undo the patch
            self.kp_asm.patch_code(address, None, None, None, None, orig_asm=[assembly], patch_data=p_orig_data, patch_comment=patch_comment, undo=True)
            del(patch_info[-1])

    # handler for Search menu
    def search(self):
        address = idc.ScreenEA()
        f = Keypatch_Search(self.kp_asm, address)
        f.Execute()
        f.Free()

    # handler for Patcher menu
    def patcher(self):
        # be sure that this arch is supported by Keystone
        if self.kp_asm.arch is None:
            idc.Warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!")
            return

        selection, addr_begin, addr_end = idaapi.read_selection()
        if selection:
            # call Fill Range function on this selected code
            return self.fill_range()

        address = idc.ScreenEA()

        if self.opts is None:
            self.load_configuration()

        init_assembly = None
        while True:
            f = Keypatch_Patcher(self.kp_asm, address, assembly=init_assembly, opts=self.opts)
            ok = f.Execute()
            if ok == 1:
                try:
                    syntax = None
                    if f.kp_asm.arch == KS_ARCH_X86:
                        syntax_id = f.c_syntax.value
                        syntax = self.kp_asm.get_syntax_by_idx(syntax_id)

                    assembly = f.c_assembly.value
                    self.opts = f.get_opts('c_opt_chk')
                    padding = (self.opts.get("c_opt_padding", 0) != 0)
                    comment = (self.opts.get("c_opt_comment", 0) != 0)

                    raw_assembly = self.kp_asm.ida_resolve(assembly, address)

                    print("Keypatch: attempting to modify \"{0}\" at 0x{1:X} to \"{2}\"".format(
                            self.kp_asm.ida_get_disasm(address), address, assembly))

                    length = self.kp_asm.patch_code(address, raw_assembly, syntax, padding, comment, None)
                    if length > 0:
                        # update start address pointing to the next instruction
                        init_assembly = None
                        address += length
                    else:
                        init_assembly = f.c_assembly.value
                        if length == 0:
                            idc.Warning("ERROR: Keypatch found invalid assembly [{0}]".format(assembly))
                        elif length == -1:
                            idc.Warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(address))
                        elif length == -2:
                            idc.Warning("ERROR: Keypatch can't read original data at 0x{0:X}, try again".format(address))

                except KsError as e:
                    print("Keypatch Error: {0}".format(e))
            else:   # Cancel
                break
            f.Free()

    # handler for Fill Range menu
    def fill_range(self):
        # be sure that this arch is supported by Keystone
        if self.kp_asm.arch is None:
            idc.Warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!")
            return
               
        selection, addr_begin, addr_end = idaapi.read_selection()
        if not selection:
            idc.Warning("ERROR: Keypatch requires a range to be selected for fill in, try again")
            return

        if self.opts is None:
            self.load_configuration()

        init_assembly = None
        f = Keypatch_FillRange(self.kp_asm, addr_begin, addr_end, assembly=init_assembly, opts=self.opts)
        ok = f.Execute()
        if ok == 1:
            try:
                syntax = None
                if f.kp_asm.arch == KS_ARCH_X86:
                    syntax_id = f.c_syntax.value
                    syntax = self.kp_asm.get_syntax_by_idx(syntax_id)

                assembly = f.c_assembly.value
                self.opts = f.get_opts('c_opt_chk')
                padding = (self.opts.get("c_opt_padding", 0) != 0)
                comment = (self.opts.get("c_opt_comment", 0) != 0)

                raw_assembly = self.kp_asm.ida_resolve(assembly, addr_begin)

                print("Keypatch: attempting to fill range [0x{0:X}:0x{1:X}] with \"{2}\"".format(
                    addr_begin, addr_end - 1, assembly))

                length = self.kp_asm.fill_code(addr_begin, addr_end, raw_assembly, syntax, padding, comment, None)
                if length == 0:
                    idc.Warning("ERROR: Keypatch failed to process this input.")
                    print("Keypatch: FAILED to process this input '{0}'".format(assembly))
                elif length == -1:
                    idc.Warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(addr_begin))

            except KsError as e:
                print("Keypatch Error: {0}".format(e))

        # free this form
        f.Free()

    def run(self, arg):
        self.patcher()


# register IDA plugin
def PLUGIN_ENTRY():
    return Keypatch_Plugin_t()


