# plugins

repository: patois/genmc
file: genmc.py
description: Display Hex-Rays Microcode
url: https://github.com/patois/genmc/blob/f6f85d2db8c4643b65fab168667cc1c0300c523f/genmc.py
comment: Display microcode
created_at: 2019-06-05 23:42:32+00:00
pushed_at: 2022-11-13 10:27:35+00:00
content:
# genmc - Display Hex-Rays Microcode
#
# Requires IDA and decompiler(s) >= 7.3
#
# Based on code/ideas from:
# - vds13.py from Hexrays SDK
# - https://github.com/RolfRolles/HexRaysDeob
# - https://github.com/NeatMonster/MCExplorer

__author__ = "Dennis Elser"

# -----------------------------------------------------------------------------
import os, shutil, errno, re

import ida_idaapi
import ida_bytes
import ida_range
import ida_kernwin as kw
import ida_hexrays as hr
import ida_funcs
import ida_diskio
import ida_ida
import ida_graph
import ida_lines
import ida_moves
from ida_pro import IDA_SDK_VERSION

PLUGIN_NAME = "genmc"

# -----------------------------------------------------------------------------
def get_mcode_name(mcode):
    """returns the name of the mcode_t passed in parameter."""
    for x in dir(hr):
        if x.startswith('m_'):
            if mcode == getattr(hr, x):
                return x
    return None

# -----------------------------------------------------------------------------
def get_mopt_name(mopt):
    """returns the name of the mopt_t passed in parameter."""
    for x in dir(hr):
        if x.startswith('mop_'):
            if mopt == getattr(hr, x):
                return x
    return None

# -----------------------------------------------------------------------------
def is_plugin():
    """returns True if this script is executed from within an IDA plugins
    directory, False otherwise."""
    return "__plugins__" in __name__

# -----------------------------------------------------------------------------
def get_target_filename():
    """returns destination path for plugin installation."""
    return os.path.join(
        ida_diskio.get_user_idadir(),
        "plugins",
        "%s%s" % (PLUGIN_NAME, ".py"))

# -----------------------------------------------------------------------------
def is_installed():
    """checks whether script is present in designated plugins directory."""
    return os.path.isfile(get_target_filename())

# -----------------------------------------------------------------------------
SELF = __file__
def install_plugin():
    """Installs script to IDA userdir as a plugin."""
    if is_plugin():
        kw.msg("Command not available. Plugin already installed.\n")
        return False

    src = SELF
    if is_installed():
        btnid = kw.ask_yn(kw.ASKBTN_NO,
            "File exists:\n\n%s\n\nReplace?" % get_target_filename())
        if btnid is not kw.ASKBTN_YES:
            return False
    dst = get_target_filename()
    usrdir = os.path.dirname(dst)
    kw.msg("Copying script from \"%s\" to \"%s\" ..." % (src, usrdir))
    if not os.path.exists(usrdir):
        try:
            os.makedirs(usrdir)
        except OSError as e:
            if e.errno != errno.EEXIST:
                kw.msg("failed (mkdir)!\n")
                return False
    try:
        shutil.copy(src, dst)
    except:
        kw.msg("failed (copy)!\n")
        return False
    kw.msg(("done\n"
        "Plugin installed - please restart this IDA instance now.\n"))
    return True

# -----------------------------------------------------------------------------
def is_ida_version(min_ver_required):
    return IDA_SDK_VERSION >= min_ver_required

# -----------------------------------------------------------------------------
def is_compatible():
    """Checks whether script is compatible with current IDA and
    decompiler versions."""
    min_ida_ver = 730
    return is_ida_version(min_ida_ver) and hr.init_hexrays_plugin()

# -----------------------------------------------------------------------------
class printer_t(hr.vd_printer_t):
    """Converts microcode output to an array of strings."""
    def __init__(self, *args):
        hr.vd_printer_t.__init__(self)
        self.mc = []

    def get_mc(self):
        return self.mc

    def _print(self, indent, line):
        self.mc.append(line)
        return 1

# -----------------------------------------------------------------------------
class microcode_insnviewer_t(ida_graph.GraphViewer):
    """Displays the graph view of Hex-Rays microcode."""
    def __init__(self, mba, mmat_name, fn_name, block, serial):
        title = "Microinstruction: %s - %d.%d (%s)" % (fn_name, block, serial, mmat_name)
        ida_graph.GraphViewer.__init__(self, title, True)
        self.mblock = mba.get_mblock(block)
        self.minsn = self.get_minsn(serial)
    
    def get_minsn(self, serial):
        curr = self.mblock.head
        for i in range(serial):
            curr = curr.next
        return curr

    def _insert_mop(self, mop, parent):
        if mop.t == hr.mop_z:
            return -1

        text = get_mopt_name(mop.t) + '\n' + mop._print()
        node_id = self.AddNode(text)
        self.AddEdge(parent, node_id)

        if mop.t == hr.mop_d: # result of another instruction
            dst_id = self._insert_minsn(mop.d)
            if dst_id:
                self.AddEdge(node_id, dst_id)
        elif mop.t == hr.mop_f: # list of arguments
            for arg in mop.f.args:
                self._insert_mop(arg, node_id)
        elif mop.t == hr.mop_a: # mop_addr_t: address of operand
            self._insert_mop(mop.a, node_id)
        elif mop.t == hr.mop_p: # operand pair
            self._insert_mop(mop.pair.lop, node_id)
            self._insert_mop(mop.pair.hop, node_id)
        return node_id

    def _insert_minsn(self, minsn):
        if minsn:
            text = get_mcode_name(minsn.opcode) + '\n' + minsn._print()
            node_id = self.AddNode(text)
            
            self._insert_mop(minsn.l, node_id)
            self._insert_mop(minsn.r, node_id)
            self._insert_mop(minsn.d, node_id)
            return node_id
        return None

    def OnRefresh(self):
        self.Clear()
        self._insert_minsn(self.minsn)
        return True

    def OnGetText(self, node_id):
        return self._nodes[node_id]

# -----------------------------------------------------------------------------
class microcode_graphviewer_t(ida_graph.GraphViewer):
    """Displays the graph view of Hex-Rays microcode."""
    def __init__(self, mba, title, lines):
        title = "Microcode graph: %s" % title
        ida_graph.GraphViewer.__init__(self, title, True)
        self._mba = mba
        self._mba.set_mba_flags(hr.MBA_SHORT)
        self._process_lines(lines)
        if mba.maturity == hr.MMAT_GENERATED or mba.maturity == hr.MMAT_PREOPTIMIZED:
            mba.build_graph()
    
    def _process_lines(self, lines):
        self._blockcmts = {}
        curblk = "-1"
        self._blockcmts[curblk] = []
        for i, line in enumerate(lines):
            plain_line = ida_lines.tag_remove(line).lstrip()
            if plain_line.startswith(';'):
                # msg("%s" % plain_line)
                re_ret = re.findall("BLOCK ([0-9]+) ", plain_line)
                if len(re_ret) > 0:
                    curblk = re_ret[0]
                    self._blockcmts[curblk] = [line]
                else:
                    self._blockcmts[curblk].append(line)
        if "0" in self._blockcmts:
            self._blockcmts["0"] = self._blockcmts["-1"] + self._blockcmts["0"]
        del self._blockcmts["-1"]

    def OnRefresh(self):
        self.Clear()
        qty = self._mba.qty
        for src in range(qty):
            self.AddNode(src)
        for src in range(qty):
            mblock = self._mba.get_mblock(src)
            for dest in mblock.succset:
                self.AddEdge(src, dest)
        return True

    def OnGetText(self, node):
        mblock = self._mba.get_mblock(node)
        vp = hr.qstring_printer_t(None, True)
        mblock._print(vp)
        
        node_key = "%d" % node
        if node_key in self._blockcmts:
            return ''.join(self._blockcmts[node_key]) + vp.s
        else:
            return vp.s

# -----------------------------------------------------------------------------
class microcode_viewer_t(kw.simplecustviewer_t):
    """Creates a widget that displays Hex-Rays microcode."""
    def Create(self, mba, title, mmat_name, fn_name, lines = []):
        self.title = "Microcode: %s" % title
        self._mba = mba
        self.mmat_name = mmat_name
        self.fn_name = fn_name
        if not kw.simplecustviewer_t.Create(self, self.title):
            return False
        self.lines = lines
        for line in lines:
            self.AddLine(line)
        return True

    def _fit_graph(self, graph):
        if graph:
            gv = graph.GetWidget()
            ida_graph.viewer_fit_window(gv)
            ida_graph.refresh_viewer(gv)
            return True
        return False

    def _dock_widgets(self, graph, dockpos=kw.DP_RIGHT):
        if graph:
            gv = graph.GetWidget()
            kw.set_dock_pos(kw.get_widget_title(gv), self.title, dockpos)

            gli = ida_moves.graph_location_info_t()
            if ida_graph.viewer_get_gli(gli, gv):
                gli.zoom = 0 # auto-position
                ida_graph.viewer_set_gli(gv, gli, ida_graph.GLICTL_CENTER)
                ida_graph.refresh_viewer(gv)
            return True
        return False

    """TODO: it's better to handle keyboard input by
    registering an "action" and assigning it a hotkey"""
    def OnKeydown(self, vkey, shift):
        if vkey == ord("G"):
            g = microcode_graphviewer_t(self._mba, self.title, self.lines)
            if g:
                g.Show()
                self._fit_graph(g)
                self._dock_widgets(g,
                    dockpos=kw.DP_FLOATING if shift else kw.DP_RIGHT)
            return True
        elif vkey == ord("I"):
            """TODO: at some point, the textual representation of the mba
                 should manually be created.
              -> we would no longer have to parse the textual output
                 that is created by the gen_microcode() function
              .> we may insert COLOR_ADDR tags which would allow us to
                 contextually link different viewers"""
            widget = self.GetWidget()
            line = kw.get_custom_viewer_curline(widget, False)
            line = ida_lines.tag_remove(line)
            p = line.find(" ")
            if p != -1 and '.' in line[:p]:
                block, serial = line.split('.')[:2]
                serial = serial.strip().split(' ')[0]
                g = microcode_insnviewer_t(self._mba, self.mmat_name, self.fn_name, int(block), int(serial))
                if g:
                    g.Show()
                    self._fit_graph(g)
                    self._dock_widgets(g,
                        dockpos=kw.DP_FLOATING if shift else kw.DP_TAB)
            else:
                message = ("There is something wrong with the output generated by gen_microcode()!\n"
                    "Please rerun '%s.py'!" % PLUGIN_NAME)
                if line.startswith(";") or not(len(line)):
                    message = "Please position the cursor on a microcode instruction."
                kw.warning(message)
            return True
        return False

# -----------------------------------------------------------------------------
def ask_desired_maturity():
    """Displays a dialog which lets the user choose a maturity level
    of the microcode to generate."""
    maturity_levels = [
    ["MMAT_GENERATED", hr.MMAT_GENERATED],
    ["MMAT_PREOPTIMIZED", hr.MMAT_PREOPTIMIZED],
    ["MMAT_LOCOPT", hr.MMAT_LOCOPT],
    ["MMAT_CALLS", hr.MMAT_CALLS],
    ["MMAT_GLBOPT1", hr.MMAT_GLBOPT1],
    ["MMAT_GLBOPT2", hr.MMAT_GLBOPT2],
    ["MMAT_GLBOPT3", hr.MMAT_GLBOPT3],
    ["MMAT_LVARS", hr.MMAT_LVARS]]

    class MaturityForm(kw.Form):
        def __init__(self):
            self.title = "Display Microcode"
            form = ("STARTITEM {id:mat_lvl}\n"
                "%s\n"
                " \n"
                "<Maturity level:{mat_lvl}>\n\n"
                "<##Options##Output includes comments:{flags_short}>{chkgroup_flags}>\n\n" %
                self.title)

            dropdown_ctl = kw.Form.DropdownListControl(
                [text for text, _ in maturity_levels])
            chk_ctl = kw.Form.ChkGroupControl(("flags_short",))

            controls = {"mat_lvl": dropdown_ctl,
            "chkgroup_flags": chk_ctl}

            kw.Form.__init__(self, form, controls)

    form = MaturityForm()
    form, args = form.Compile()
    form.flags_short.checked = True
    ok = form.Execute()

    mmat = None
    text = None
    flags = 0
    if ok == 1:
        text, mmat = maturity_levels[form.mat_lvl.value]
    flags |= 0 if form.flags_short.checked else hr.MBA_SHORT
    form.Free()
    return (text, mmat, flags)

# -----------------------------------------------------------------------------
def show_microcode():
    """Generates and displays microcode for an address range.
    An address range can be a selection of code or that of
    the current function."""
    sel, sea, eea = kw.read_range_selection(None)
    pfn = ida_funcs.get_func(kw.get_screen_ea())
    if not sel and not pfn:
        return (False, "Position cursor within a function or select range")

    if not sel and pfn:
        sea = pfn.start_ea
        eea = pfn.end_ea

    addr_fmt = "%016x" if ida_ida.inf_is_64bit() else "%08x"
    fn_name = (ida_funcs.get_func_name(pfn.start_ea)
        if pfn else "0x%s-0x%s" % (addr_fmt % sea, addr_fmt % eea))

    F = ida_bytes.get_flags(sea)
    if not ida_bytes.is_code(F):
        return (False, "The selected range must start with an instruction")

    text, mmat, mba_flags = ask_desired_maturity()
    if text is None and mmat is None:
        return (True, "Cancelled")
    
    if not sel and pfn:
        mbr = hr.mba_ranges_t(pfn)
    else:
        mbr = hr.mba_ranges_t()
        mbr.ranges.push_back(ida_range.range_t(sea, eea))

    hf = hr.hexrays_failure_t()
    ml = hr.mlist_t()
    mba = hr.gen_microcode(mbr, hf, ml, hr.DECOMP_WARNINGS, mmat)
    if not mba:
        return (False, "0x%s: %s" % (addr_fmt % hf.errea, hf.desc()))
    vp = printer_t()
    mba.set_mba_flags(mba.get_mba_flags() | mba_flags)
    mba._print(vp)
    mcv = microcode_viewer_t()
    if not mcv.Create(mba, "%s (%s)" % (fn_name, text), text, fn_name, vp.get_mc()):
        return (False, "Error creating viewer")

    mcv.Show()
    return (True,
        "Successfully generated microcode for 0x%s..0x%s" % (addr_fmt % sea, addr_fmt % eea))

# -----------------------------------------------------------------------------
def create_mc_widget():
    """Checks minimum requirements for the script/plugin to be able to run.
    Displays microcode or in case of failure, displays error message.
    This function acts as the main entry point that is invoked if the
    code is run as a script or as a plugin."""
    if not is_compatible():
        kw.msg("%s: Unsupported IDA / Hex-rays version\n" % (PLUGIN_NAME))
        return False
    success, message = show_microcode()
    output = kw.msg if success else kw.warning
    output("%s: %s\n" % (PLUGIN_NAME, message))
    return success

# -----------------------------------------------------------------------------
class genmc(ida_idaapi.plugin_t):
    """Class that is required for the code to be recognized as
    a plugin by IDA."""
    flags = 0
    comment = "Display microcode"
    help = comment
    wanted_name = PLUGIN_NAME
    wanted_hotkey = "Ctrl-Shift-M"

    def init(self):
        return (ida_idaapi.PLUGIN_OK if
            is_compatible() else ida_idaapi.PLUGIN_SKIP)

    def run(self, arg):
        create_mc_widget()

    def term(self):
        pass

# -----------------------------------------------------------------------------
def PLUGIN_ENTRY():
    """Entry point of this code if launched as a plugin."""
    return genmc()

# -----------------------------------------------------------------------------
def SCRIPT_ENTRY():
    """Entry point of this code if launched as a script."""
    if not is_plugin():
        if not is_installed():
            kw.msg(("%s: Available commands:\n"
                "[+] \"install_plugin()\" - install script to ida_userdir/plugins\n") % (
                PLUGIN_NAME))
        create_mc_widget()
        return True
    return False

# -----------------------------------------------------------------------------
SCRIPT_ENTRY()


repository: OALabs/findyara-ida
file: findyara.py
description: IDA python plugin to scan binary with Yara rules
url: https://github.com/OALabs/findyara-ida/blob/6ffb82f5e5aa6610050e0d1aabd8d8eb7428dfb1/findyara.py
comment: FindYara plugin for IDA Pro (using yara framework)
created_at: 2017-12-03 23:37:08+00:00
pushed_at: 2024-01-30 12:39:58+00:00
content:
########################################################################################
##
## All credit to David Berard (@_p0ly_) https://github.com/polymorf/findcrypt-yara
##
## This plugin is simply a copy of his excellent findcrypt-yara plugin only expanded
## use allow searching for any yara rules.
##
##  ____ __ __  __ ____   _  _  ___  ____   ___
## ||    || ||\ || || \\  \\// // \\ || \\ // \\
## ||==  || ||\\|| ||  ))  )/  ||=|| ||_// ||=||
## ||    || || \|| ||_//  //   || || || \\ || ||
##
## IDA plugin for Yara scanning... find those Yara matches!
##
## Updated for IDA 7.xx and Python 3
##
## To install:
##      Copy script into plugins directory, i.e: C:\Program Files\<ida version>\plugins
##
## To run:
##      Ctl+Alt+Y or Edit->Plugins->FindYara
##      Use the dialogue box to select your yara rule file and start scanning!
##
########################################################################################

import idaapi
import idautils
import ida_bytes
import idc
import ida_kernwin
import yara
import string

__AUTHOR__ = '@herrcore'

PLUGIN_NAME = "FindYara"
PLUGIN_HOTKEY = "Ctrl-Alt-Y"
VERSION = '3.3.0'

try:
    class Kp_Menu_Context(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)


        @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):
            if ctx.form_type == idaapi.BWN_DISASM:
                return idaapi.AST_ENABLE_FOR_WIDGET
            return idaapi.AST_DISABLE_FOR_WIDGET


    class Searcher(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.search()
            return 1

except:
    pass

p_initialized = False



class YaraSearchResultChooser(idaapi.Choose):
    def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False):
        idaapi.Choose.__init__(
            self,
            title,
            [
                ["Address", idaapi.Choose.CHCOL_HEX|10],
                ["Rule Name", idaapi.Choose.CHCOL_PLAIN|20],
                ["Match Name", idaapi.Choose.CHCOL_PLAIN|20],
                ["Match", idaapi.Choose.CHCOL_PLAIN|40],
                ["Type", idaapi.Choose.CHCOL_PLAIN|10],
            ],
            flags=flags,
            width=width,
            height=height,
            embedded=embedded)
        self.items = items
        self.selcount = 0
        self.n = len(items)


    def OnClose(self):
        return


    def OnSelectLine(self, n):
        self.selcount += 1
        ida_kernwin.jumpto(self.items[n][0])


    def OnGetLine(self, n):
        res = self.items[n]
        res = [idc.atoa(res[0]), res[1], res[2], res[3], res[4]]
        return res


    def OnGetSize(self):
        n = len(self.items)
        return n


    def show(self):
        return self.Show() >= 0

#--------------------------------------------------------------------------
# Plugin
#--------------------------------------------------------------------------
class FindYara_Plugin_t(idaapi.plugin_t):
    comment = "FindYara plugin for IDA Pro (using yara framework)"
    help = ""
    wanted_name = PLUGIN_NAME
    wanted_hotkey = PLUGIN_HOTKEY
    flags = idaapi.PLUGIN_KEEP


    def init(self):
        global p_initialized

        # register popup menu handlers
        try:
            Searcher.register(self, "FindYara")
        except:
            pass

        if p_initialized is False:
            p_initialized = True
            idaapi.register_action(idaapi.action_desc_t(
                "FindYara",
                "Find Yara rule matches!",
                self.search,
                None,
                None,
                0))
            idaapi.attach_action_to_menu("Edit/FindYara", "FindYara", idaapi.SETMENU_APP)
            ## Print a nice header
            print("=" * 80)
            print("  ____ __ __  __ ____   _  _  ___  ____   ___ ")
            print(r" ||    || ||\ || || \\  \\// // \\ || \\ // \\")
            print(r" ||==  || ||\\|| ||  ))  )/  ||=|| ||_// ||=||")
            print(r" ||    || || \|| ||_//  //   || || || \\ || ||")
            print("\nFindYara v{0} by @herrcore".format(VERSION))
            print("* All credit to David Berard (@_p0ly_) for the code! *")
            print("* This is a slightly modified version of findcrypt-yara *")
            print("\nFindYara search shortcut key is Ctrl-Alt-y")
            print("=" * 80)

        return idaapi.PLUGIN_KEEP


    def term(self):
        pass


    def toVirtualAddress(self, offset, segments):
        va_offset = 0
        for seg in segments:
            if seg[1] <= offset < seg[2]:
                va_offset = seg[0] + (offset - seg[1])
        return va_offset


    def search(self, yara_file):
        memory, offsets = self._get_memory()
        try:
            rules = yara.compile(yara_file)
        except:
            print("ERROR: Cannot compile Yara rules from %s" % yara_file)
            return
        values = self.yarasearch(memory, offsets, rules)
        c = YaraSearchResultChooser("FindYara scan results", values)
        r = c.show()


    def yarasearch(self, memory, offsets, rules):
        values = list()
        matches = rules.match(data=memory)
        for rule_match in matches:
            name = rule_match.rule
            for match in rule_match.strings:
                match_string = match[2]
                match_type = 'unknown'
                if all(chr(c) in string.printable for c in match_string):
                    match_string = match_string.decode('utf-8')
                    match_type = 'ascii string'
                elif all(chr(c) in string.printable+'\x00' for c in match_string) and (b'\x00\x00' not in match_string):
                     match_string = match_string.decode('utf-16')
                     match_type = 'wide string'
                else:
                    match_string = " ".join("{:02x}".format(c) for c in match_string)
                    match_type = 'binary'

                value = [
                    self.toVirtualAddress(match[0], offsets),
                    name,
                    match[1],
                    match_string,
                    match_type
                ]
                values.append(value)
        return values


    def _get_memory(self):
        result = bytearray()
        segment_starts = [ea for ea in idautils.Segments()]
        offsets = []
        start_len = 0
        for start in segment_starts:
            end = idc.get_segm_attr(start, idc.SEGATTR_END)
            result += ida_bytes.get_bytes(start, end - start)
            offsets.append((start, start_len, len(result)))
            start_len = len(result)
        return bytes(result), offsets


    def run(self, arg):
        yara_file = ida_kernwin.ask_file(0, "*.yara", 'Choose Yara File...')
        if yara_file == None:
            print("ERROR: You must choose a yara file to scan with")
        else:
            self.search(yara_file)


# register IDA plugin
def PLUGIN_ENTRY():
    return FindYara_Plugin_t()


repository: vmallet/ida-hex-highlighter
file: plugins/highlighter_plugin.py
description: Hex-Rays Block Highlighter plugin for IDA to highlight if/for/do/switch/while blocks
wanted_name: Hex-Rays Block Highlighter
comment: Highlight if/for/do/while/switch blocks in Pseudocode windows
created_at: 2022-04-06 22:13:03+00:00
pushed_at: 2022-04-17 05:38:04+00:00
url:
https://github.com/vmallet/ida-hex-highlighter/blob/a160bc6fd304c272a0f631777a2dac110465a2f2/plugins/highlighter_plugin.py

content:
# SPDX-FileCopyrightText: 2022 Vincent Mallet <vmallet@gmail.com>
# SPDX-License-Identifier: MIT

"""
Hex-Rays Block Highlighter plugin.

Click on a do/while/for/if/switch keyword and highlight the
corresponding block.

Can be enabled/disabled by right-clicking in the Pseudocode window
and choosing "Highlighting on/off"
"""

import ida_hexrays
import ida_idaapi

from ida_hex_highlighter.idav_hex_highlighter import partial_init

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


class HighlighterPlugin(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE | ida_idaapi.PLUGIN_PROC
    wanted_name = "Hex-Rays Block Highlighter"
    wanted_hotkey = ""
    comment = "Highlight if/for/do/while/switch blocks in Pseudocode windows"
    help = ""

    def __init__(self):
        self.block_highlighter = None

    def init(self):
        if not ida_hexrays.init_hexrays_plugin():
            return

        self.block_highlighter, _ = partial_init(False)
        self.block_highlighter.hook()

        return ida_idaapi.PLUGIN_KEEP  # keep us in the memory

    def term(self):
        if self.block_highlighter:
            self.block_highlighter.unhook()
            self.block_highlighter = None

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return HighlighterPlugin()


repository: eybisi/d810
file: D810.py
url: https://github.com/eybisi/d810/blob/2f888dac3bcddf73a82c8817c30be7b5887fdc78/D810.py
wanted_name: D-810
comment: Interface to the D-810 plugin
created_at: 2022-10-20 15:08:52+00:00
pushed_at: 2022-10-25 20:44:00+00:00
content:
import os
import idaapi
import ida_hexrays
import ida_kernwin


from d810.conf import D810Configuration
from d810.manager import D810State, D810_LOG_DIR_NAME
from d810.log import configure_loggers, clear_logs


D810_VERSION = "0.1"

class D810Plugin(idaapi.plugin_t):
    # variables required by IDA
    flags = 0  # normal plugin
    wanted_name = "D-810"
    wanted_hotkey = "Ctrl-Shift-D"
    comment = "Interface to the D-810 plugin"
    help = ""
    initialized = False

    def __init__(self):
        super(D810Plugin, self).__init__()
        self.d810_config = None
        self.state = None
        self.initialized = False


    def reload_plugin(self):
        if self.initialized:
            self.term()

        self.d810_config = D810Configuration()

        #TO-DO: if [...].get raises an exception because log_dir is not found, handle exception
        real_log_dir = os.path.join(self.d810_config.get("log_dir"), D810_LOG_DIR_NAME)

        #TO-DO: if [...].get raises an exception because erase_logs_on_reload is not found, handle exception
        if self.d810_config.get("erase_logs_on_reload"):
            clear_logs(real_log_dir)

        configure_loggers(real_log_dir)
        self.state = D810State(self.d810_config)
        breakpoint()
        print("D-810 reloading...")
        self.state.start_plugin()
        self.initialized = True


    # IDA API methods: init, run, term
    def init(self):
        if not ida_hexrays.init_hexrays_plugin():
            print("D-810 need Hex-Rays decompiler. Skipping")
            return idaapi.PLUGIN_SKIP

        kv = ida_kernwin.get_kernel_version().split(".")
        if (int(kv[0]) < 7) or ((int(kv[0]) == 7) and (int(kv[1]) < 5)):
            print("D-810 need IDA version >= 7.5. Skipping")
            return idaapi.PLUGIN_SKIP
        print("D-810 initialized (version {0})".format(D810_VERSION))
        return idaapi.PLUGIN_OK


    def run(self, args):
        self.reload_plugin()


    def term(self):
        print("Terminating D-810...")
        if self.state is not None:
            self.state.stop_plugin()

        self.initialized = False


def PLUGIN_ENTRY():
    return D810Plugin()


repository: sam-b/ida-scripts
file: neo4ida/neo4ida.py
description: Dumping ground for whatever IDA Pro scripts I write
wanted_name: Neo4IDA
comment: Neo4j graph export and query interface
created_at: 2015-10-31 19:10:30+00:00
pushed_at: 2016-10-09 21:54:43+00:00
url:
https://github.com/sam-b/ida-scripts/blob/e40cf6394c6e81ede9aad7e9830d0936f0b49e0f/neo4ida/neo4ida.py

content:
import json
import os
import time
import hashlib
import inspect

import idc
import idautils
import idaapi

from idaapi import Form

from py2neo import authenticate, Graph, Node, Relationship

class ConnectionManagementForm(Form):
	def __init__(self,manager):
		self.manager = manager
		self.conf = manager.get_config()
		self.changed = False
		Form.__init__(self, 
			"""Neo4IDA - Manage Neo4j Connection
			{form_change}
			<#Host#~H~ost:{host}> <#Port#~P~ort:{port}>
			<#Username#~U~sername:{username}>
			<#Password#~P~assword:{password}>
			"""
			, {
				"form_change": Form.FormChangeCb(self.form_change),
				"host":Form.StringInput(swidth=20),
				"port":Form.StringInput(swidth=10),
				"username":Form.StringInput(swidth=40),
				"password":Form.StringInput(swidth=40)
			}
		)
		
		self.Compile()
		self.host.value = self.conf["host"]
		self.port.value = self.conf["port"]
		self.username.value = self.conf["username"]
		self.password.value = self.conf["password"]
		self.Execute()
	
	def form_change(self,fid):
		if fid == self.host.id:
			tmp = self.GetControlValue(self.host)
			self.host.value = tmp
			self.changed = True
		if fid == self.port.id:
			tmp = self.GetControlValue(self.port)
			self.port.value = tmp
			self.changed = True
		if fid == self.username.id:
			tmp = self.GetControlValue(self.username)
			self.username.value = tmp
			self.changed = True
		if fid == self.password.id:
			tmp = self.GetControlValue(self.password)
			self.password.value = tmp
			self.changed = True
		if fid == -2:
			if self.changed:
				new_conf = {}
				new_conf['host'] = self.host.value
				new_conf['port'] = self.port.value
				new_conf['username'] = self.username.value
				new_conf['password'] = self.password.value
				self.manager.update_config(new_conf)
				self.conf = new_conf
				print "Config updated"
				self.manager.connect()
			self.Close(-1)

class CypherQueryForm(Form):
	def __init__(self,manager):
		self.manager = manager
		self.conf = manager.get_config()
		self.changed = False
		Form.__init__(self, 
			"""Neo4IDA - Execute Cypher Query
			{form_change}
			<#Query#~Q~uery:{query}>
			<#Execute Query#~E~xecute:{executeButton}>
			"""
			, {
				"form_change": Form.FormChangeCb(self.form_change),
				"query":Form.StringInput(swidth=80),
				"executeButton":Form.ButtonInput(self.button_press)
			}
		)
		
		self.Compile()
		self.query.value = "START n=node(*) return n;"
		self.Execute()
	
	def form_change(self,fid):
		if fid == self.query.id:
			query = self.GetControlValue(self.query)
			self.query.value = query
		if fid == -2:
			self.Close(-1)
	
	def button_press(self,fid):
		print self.query.value
		for i in self.manager.neo.cypher.execute(self.query.value):
			print i
			
class UiAction(idaapi.action_handler_t):
    def __init__(self, id, name, tooltip, menuPath, callback, icon):
        idaapi.action_handler_t.__init__(self)
        self.id = id
        self.name = name
        self.tooltip = tooltip
        self.menuPath = menuPath
        self.callback = callback
        scriptPath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
        self.icon = idaapi.load_custom_icon(
            scriptPath + "/" + "icon" + ".png"
        )

    def registerAction(self):
        action_desc = idaapi.action_desc_t(
        self.id,
        self.name,
        self,
		"",
        self.tooltip,
		self.icon
		)      
        if not idaapi.register_action(action_desc):
            return False
        if not idaapi.attach_action_to_menu(self.menuPath, self.id, 0):
            return False
        if not idaapi.attach_action_to_toolbar("AnalysisToolBar", self.id):
            return False
        return True

    def unregisterAction(self):
        idaapi.detach_action_from_menu(self.menuPath, self.id)
        idaapi.unregister_action(self.id)

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

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

class neo4ida_t(idaapi.plugin_t):
	flags = 0
	comment = "Neo4j graph export and query interface"
	help = "Neo4j graph export and query interface"
	wanted_name = "Neo4IDA"
	wanted_hotkey = ""

	def init(self):
		self.conf_file = os.path.expanduser("~") + os.path.sep + "neo4ida.json"
		config = self.get_config()
		if not config:
			config = self.create_default_config()
		self.connect()
		action = UiAction(
			id="neo4ida:upload",
			name="Upload",
			tooltip="Upload to neo4j",
			menuPath="Edit/neo4ida/",
			callback=self.upload,
			icon=""
		)
		if not action.registerAction():
			return 1
		action = UiAction(
			id="neo4ida:dropdb",
			name="Drop Database",
			tooltip="Delete all entries in database instance.",
			menuPath="Edit/neo4ida/",
			callback=self.drop_db,
			icon=""
		)
		if not action.registerAction():
			return 1
		action = UiAction(
			id="neo4ida:config",
			name="Configure",
			tooltip="Configure neo4j connection details.",
			menuPath="Edit/neo4ida/",
			callback=self.config_form,
			icon=""
		)
		if not action.registerAction():
			return 1
		action = UiAction(
			id="neo4ida:query",
			name="Cypher Query",
			tooltip="Execute a Cypher query.",
			menuPath="Edit/neo4ida/",
			callback=self.query_form,
			icon=""
		)
		if not action.registerAction():
			return 1
		action = UiAction(
			id="neo4ida:browser",
			name="Neo4j Browser",
			tooltip="Open Neo4j browser.",
			menuPath="Edit/neo4ida/",
			callback=self.open_browser,
			icon=""
		)
		if not action.registerAction():
			return 1
		action = UiAction(
			id="neo4ida:diff",
			name="Binary Diff",
			tooltip="Open binary diffing interface.",
			menuPath="Edit/neo4ida/",
			callback=self.binary_diff,
			icon=""
		)
		if not action.registerAction():
			return 1
		return idaapi.PLUGIN_KEEP

		
	def connect(self):
		conf = self.get_config()
		authenticate(conf['host'] + ":" + conf['port'],conf['username'],conf["password"])
		try:
			self.neo = Graph("http://" + conf['host'] + ":" + conf["port"] + "/db/data")
		except:
			print "Failed to connect!"
	
	def term(self):
		return None

	def binary_diff(self,ctf):
		print "Open binary diffing interface"

	def drop_db(self,ctx):
		self.neo.cypher.execute("START n=node(*) detach delete n;")
		print "All database nodes and relationships deleted."
	
	def open_browser(self,ctx):
		self.neo.open_browser()
	
	def config_form(self,ctx):
		ConnectionManagementForm(self)
	
	def query_form(self,ctf):
		CypherQueryForm(self)
	
	def upload(self,ctx):
		start = time.time()
		func_count = 0
		bb_count = 0
		call_count = 0
		target = idaapi.get_root_filename()
		hash = idc.GetInputMD5()
		tx = self.neo.cypher.begin()
		insert_binary = "MERGE (n:Binary {name:{N},hash:{H}}) RETURN n"
		insert_func = "MERGE (n:Function {name:{N},start:{S},flags:{F}}) RETURN n"
		insert_bb = "MERGE (n:BasicBlock {start:{S}, end:{E}}) RETURN n"
		create_relationship = "MATCH (u:Function {name:{N}}), (r:Function {start:{S}}) CREATE (u)-[:CALLS]->(r)"
		create_contains = "MATCH (u:BasicBlock {start:{S}}), (f:Function {name:{N}}) CREATE (f)-[:CONTAINS]->(u)"
		create_inside = "MATCH (u:Function {start:{S}}), (b:Binary {hash:{H}}) CREATE (f)-[:INSIDE]->(b)"
		self.neo.cypher.execute(insert_binary, {"N":target, "H":hash})
		self.neo.cypher.execute("CREATE INDEX ON :Function(start)")
		#self.neo.cypher.execute("CREATE INDEX ON :Function(name)")
		self.neo.cypher.execute("CREATE INDEX ON :BasicBlock(start)")
		for f in Functions():
			tx.append(create_inside, {"S":f, "H":hash})
			callee_name = GetFunctionName(f)
			flags = get_flags(f)
			type = GetType(f)
			if type:
				return_type = type.split()[0]
				print type
				end_return = type.find(' ')
				start_args = type.find('(')
				print type[end_return +1:start_args]
				print type[start_args+1:].split(',')
			else:
				print GuessType(f)
			tx.append(insert_func, {"N": callee_name, "S":f, "F":flags})
			func_count += 1
			fc = idaapi.FlowChart(idaapi.get_func(f))
			for block in fc:
				tx.append(insert_bb, {"S":block.startEA,"E":block.endEA})
				tx.append(create_contains,{"S":block.startEA,"N":f})
				bb_count += 1
		tx.process()
		tx.commit()
		tx = self.neo.cypher.begin()
		for f in Functions():
			for xref in CodeRefsTo(f,0):
				caller_name = GetFunctionName(xref)
				if caller_name != '':
					tx.append(create_relationship,{"N":caller_name,"S":f})
					call_count += 1
		tx.process()
		tx.commit()
		print "Upload ran in: " + str(time.time() - start)
		print "Uploaded " + str(func_count) + " functions, " + str(call_count) +" function calls and " + str(bb_count) + " basic blocks."
	
	def run(self):
		pass
	
	def update_config(self,new_config):
		print "updating config to be: "
		print json.dumps(new_config)
		os.remove(self.conf_file)
		with open(self.conf_file,"w+") as f:
			f.write(json.dumps(new_config))
	
	def create_default_config(self):
		default_conf = {
			"host": "localhost",
			"port": "7474",
			"username":"neo4j",
			"password":"neo4j"
		}
		with open(self.conf_file,"w+") as f:
			f.write(json.dumps(default_conf))
		return default_conf
	
	def get_config(self):
		try:
			with open(self.conf_file,"r") as f:
				return json.loads(f.read())
		except:
			return None

	def find_path(self,startFunc, endFunc):
		all_paths = ""
		print "Finding all paths from " + startFunc + " to " + endFunc
		self.neo.cypher.execute(all_paths,{})
	
def help():
	print "Upload: Upload graph to neo instance."
	print "Drop Database: Delete all nodes and relationships in the neo4j instance."
	print "Configure: Update your connection configuration to the neo4j instance."
	print "Cypher Query: Execute arbitary cypher queries."
	print "Neo4j Browser: Open the Neo4j web interface in your systems default browser."
	print "Binary Diff: placeholer menu item."

def get_args(f):
  local_variables = [ ]
  arguments = [ ]
  current = local_variables

  frame = idc.GetFrame(f)
  arg_string = ""
  if frame == None:
    return None
        
  start = idc.GetFirstMember(frame)
  end = idc.GetLastMember(frame)
  count = 0
  max_count = 10000
  args_str = ""     
  while start <= end and count <= max_count:
    size = idc.GetMemberSize(frame, start)
    count = count + 1
    if size == None:
      start = start + 1
      continue

    name = idc.GetMemberName(frame, start)  
    start += size
            
    if name in [" r", " s"]:
      # Skip return address and base pointer
      current = arguments
      continue
    arg_string += " " + name
    current.append(name)
  if len(arguments) == 0:
    arguments.append("void")
  return arguments	

def get_flags(f):
	out = []
	flags = idc.GetFunctionFlags(f)
	if flags & FUNC_NORET: 
		out.append("FUNC_NORET")
	if flags & FUNC_FAR: 
		out.append("FUNC_FAR")
	if flags & FUNC_LIB: 
		out.append("FUNC_LIB")
	if flags & FUNC_STATIC: 
		out.append("FUNC_STATIC")
	if flags & FUNC_FRAME: 
		out.append("FUNC_FRAME")
	if flags & FUNC_USERFAR:  
		out.append("FUNC_USERFAR") 
	if flags & FUNC_HIDDEN:
		out.append("FUNC_HIDDEN")
	if flags & FUNC_THUNK:  
		out.append("FUNC_THUNK")
	if flags & FUNC_LIB:
		out.append("FUNC_BOTTOMBP")
	return out
	
def PLUGIN_ENTRY():
    return neo4ida_t()


repository: toshipiazza/ghidra-plugin-hexagon
file: GhidraBuild/IDAPro/Python/7xx/plugins/xml_exporter.py
description: Hexagon decompiler for Ghidra
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2022-01-15 03:46:25+00:00
pushed_at: 2022-02-15 08:27:42+00:00
url:
https://github.com/toshipiazza/ghidra-plugin-hexagon/blob/be29246fc234ece766a5719564bb55ccf69bc1e7/GhidraBuild/IDAPro/Python/7xx/plugins/xml_exporter.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.
##
#---------------------------------------------------------------------
# xmlexp.py - IDA XML Exporter plugin
#---------------------------------------------------------------------
"""
Plugin for IDA which exports a XML PROGRAM document file from a database.
This file must be placed in the IDA plugins directory.
The file idaxml.py must be placed in the IDA python directory.
"""

import ida_auto
import ida_idaapi
import ida_kernwin
import idaxml
import idc
import sys


class XmlExporterPlugin(ida_idaapi.plugin_t):
    """
    XML Exporter plugin class
    """
    flags = 0
    comment = "Export database as XML file"
    help = "Export database as XML <PROGRAM> document"
    wanted_name = "XML Exporter"
    wanted_hotkey = "Ctrl-Shift-x"


    def init(self):
        """
        init function for XML Exporter 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 Exporter plugin.
        
        Args:
            arg: Integer, 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.XmlExporter(arg)
        try:
            try:
                xml.export_xml()
            except idaxml.Cancelled:
                ida_kernwin.hide_wait_box()
                msg = "XML Export cancelled!"
                print "\n" + msg
                idc.warning(msg)
            except:
                ida_kernwin.hide_wait_box()
                msg = "***** Exception occurred: XML Exporter failed! *****"
                print "\n" + msg + "\n", sys.exc_type, sys.exc_value
                idc.warning(msg)
        finally:
            xml.cleanup()
            ida_auto.set_ida_state(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlExporterPlugin()


repository: nihilus/IDA-pinlog
file: loadlog_plugin.py
wanted_name: pinlog loadlog
comment: Load logs produced by pinlog
created_at: 2014-09-03 22:55:33+00:00
pushed_at: 2014-09-04 14:22:44+00:00
url:
https://github.com/nihilus/IDA-pinlog/blob/b0f0b323a1446deaf3136e198836edb8304fe5e1/loadlog_plugin.py

content:
#!/usr/bin/env pythong
#
# plugin to load file coming from pinlog tool
#                       deroko of ARTeam

import	idaapi
import  idc
import	struct
import  colorsys

class loadlog_plugin_t(idaapi.plugin_t):
        flags = 0
        comment     = "Load logs produced by pinlog"
        help        = "Press Ctrl+h and select log file to load"
        wanted_name = "pinlog loadlog"
        wanted_hotkey = "Ctrl+h"    
        
        def init(self):
                self.clist = self.build_color_list();
                return idaapi.PLUGIN_KEEP;
            
        def build_color_list(self):
                clist = [];
                
                r = 0x00/255.0;
                g = 0x00/255.0;
                b = 0xff/255.0;
                
                (h,s,v) = colorsys.rgb_to_hsv(r,g,b);
                s -= 0.35;
                for x in range(0,15):
                        (r,g,b) = colorsys.hsv_to_rgb(h,s,v);
                        #it's BGR
                        ida_color = (int(b*255) << 16) + (int(g*255)<<8) + int(r*255);
                        clist.append(ida_color); 
                        s -= 0.04;
                clist.reverse();       
                return clist;
        
        def run(self, arg):                
        	filepath = idaapi.askfile_c(False, "*.*", "Pin log file");
        	imagebase = idaapi.get_imagebase();
        	try:
        		f = open(filepath, "rb");
        	except:
        		print("Need log file to parse data...");
        		return;
        	buff = f.read();
        	for index in range(0, len(buff)):
        		exec_count = ord(buff[index]);
        		if exec_count == 0:
        			continue;
        		
        		exec_count = exec_count / 10;
        		if exec_count >= len(self.clist): exec_count = len(self.clist)-1;
        		        
        		ida_color = self.clist[exec_count];
        
        		idc.SetColor(imagebase + index, CIC_ITEM, ida_color);
        
        def term(self):
                pass;
                    
def PLUGIN_ENTRY():
    return loadlog_plugin_t()


repository: qilingframework/qiling
file: qiling/extensions/idaplugin/qilingida.py
description: A True Instrumentable Binary Emulation Framework
wanted_name: Qiling Emulator
created_at: 2019-08-22 13:22:15+00:00
pushed_at: 2025-05-06 02:27:20+00:00
url:
https://github.com/qilingframework/qiling/blob/a430518ef2026c3fa9d69df5831581b9ac3aa368/qiling/extensions/idaplugin/qilingida.py

content:
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework Plugin For IDA
# Learn how to use? Please visit https://docs.qiling.io/en/latest/ida/

UseAsScript = True

import sys
import collections
import time
import struct
import re
import logging
import shlex
from enum import Enum
from elftools.elf.elffile import ELFFile
from json import load

# IDA Python SDK
from idaapi import *
from idc import *
from idautils import *
import idc
import ida_ua
import ida_idaapi
import ida_funcs
import ida_nalt
import idautils
import ida_xref
import ida_kernwin
import ida_ida
import ida_bytes
import ida_segment
import ida_name
import ida_gdl
import ida_frame
import ida_idp
import ida_auto
import ida_netnode
import ida_hexrays
import ida_range
# PyQt
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import (QPushButton, QHBoxLayout)

# Qiling
from qiling import Qiling
from qiling.const import *
from qiling.arch.x86_const import reg_map_32 as x86_reg_map_32
from qiling.arch.x86_const import reg_map_64 as x86_reg_map_64
from qiling.arch.x86_const import reg_map_misc as x86_reg_map_misc
from qiling.arch.x86_const import reg_map_st as x86_reg_map_st
from qiling.arch.arm_const import reg_map as arm_reg_map
from qiling.arch.arm64_const import reg_map as arm64_reg_map
from qiling.arch.mips_const import reg_map as mips_reg_map
from qiling import __version__ as QLVERSION
from qiling.os.filestruct import ql_file
from keystone import *


QilingHomePage = 'https://www.qiling.io'
QilingStableVersionURL = 'https://raw.githubusercontent.com/qilingframework/qiling/master/qiling/__version__.py'
logging.basicConfig(level=logging.INFO, format='[%(levelname)s][%(module)s:%(lineno)d] %(message)s')

class Colors(Enum):
    Blue = 0xE8864A
    Pink = 0xC0C0FB
    White = 0xFFFFFF
    Black = 0x000000
    Green = 0xd3ead9
    Gray = 0xd9d9d9
    Beige = 0xCCF2FF

class IDA:
    def __init__(self):
        pass

    @staticmethod
    def get_function(addr):
        return ida_funcs.get_func(addr)

    @staticmethod
    def get_function_start(addr):
        return IDA.get_function(addr).start_ea

    @staticmethod
    def get_function_end(addr):
        return IDA.get_function(addr).end_ea

    @staticmethod
    def get_function_framesize(addr):
        return IDA.get_function(addr).frsize

    @staticmethod
    def get_function_name(addr):
        return ida_funcs.get_func_name(addr)

    @staticmethod
    def get_functions():
        return [IDA.get_function(func) for func in idautils.Functions()]

    @staticmethod
    def set_color(addr, what, color):
        return idc.set_color(addr, what, color)

    @staticmethod
    def color_block(bb, color):
        for i in range(bb.start_ea, bb.end_ea):
            IDA.set_color(i, idc.CIC_ITEM, color)

    # note:
    # corresponds to IDA graph view
    # a good example to iterate the graph
    # https://github.com/idapython/src/blob/bc9b51b1c70083815a574a57b7a783698de3698d/examples/core/dump_flowchart.py
    # arg can be a function or a (start, end) tuple or an address in the function
    @staticmethod
    def get_flowchart(arg):
        if type(arg) is int:
            func = IDA.get_function(arg)
            if func is None:
                return None
            return ida_gdl.FlowChart(func)
        return ida_gdl.FlowChart(arg)

    @staticmethod
    def get_block(addr):
        flowchart = IDA.get_flowchart(addr)
        for bb in flowchart:
            if bb.start_ea <= addr and addr < bb.end_ea:
                return bb
        return None

    @staticmethod
    def block_is_terminating(bb):
        # fcb_ret: has a retn instruction in the end
        # fcb_noret: in most cases, exit() is called
        # fcb_indjump: jmp $eax
        if (bb.type == ida_gdl.fcb_ret or bb.type == ida_gdl.fcb_noret or
                (bb.type == ida_gdl.fcb_indjump and len(list(bb.succs())) == 0)):
            return True
        for b in bb.succs():
            if b.type == ida_gdl.fcb_extern:
                return True
        return False

    @staticmethod
    def get_starting_block(addr):
        flowchart = IDA.get_flowchart(addr)
        if flowchart is None:
            return None
        func = IDA.get_function(addr)
        for bb in flowchart:
            if bb.start_ea == func.start_ea:
                return bb
        return None

    @staticmethod
    def get_terminating_blocks(addr):
        flowchart = IDA.get_flowchart(addr)
        return [bb for bb in flowchart if IDA.block_is_terminating(bb)]

    @staticmethod
    def get_prev_head(addr, minea=0):
        return ida_bytes.prev_head(addr, minea)

    @staticmethod
    def get_segments():
        r = []
        seg = ida_segment.get_first_seg()
        while seg is not None:
            r.append(seg)
            seg = ida_segment.get_next_seg(seg.start_ea)
        return r

    @staticmethod
    def get_segment_name(s):
        return ida_segment.get_segm_name(s)

    @staticmethod
    def get_segment_by_name(name):
        return ida_segment.get_segm_by_name(name)

    @staticmethod
    def __addr_in_seg(addr):
        segs = IDA.get_segments()
        for seg in segs:
            if addr < seg.end_ea and addr >= seg.start_ea:
                return seg
        return None

    # note: accept name and address in the segment
    @staticmethod
    def get_segment(arg):
        if type(arg) is int:
            return IDA.__addr_in_seg(arg)
        else: # str
            return IDA.get_segment_by_name(arg)

    @staticmethod
    def get_segment_start(arg):
        seg = IDA.get_segment(arg)
        if seg is not None:
            return seg.start_ea
        return None

    @staticmethod
    def get_segment_end(arg):
        seg = IDA.get_segment(arg)
        if seg is not None:
            return seg.end_ea
        return None

    @staticmethod
    def get_segment_perm(arg):
        seg = IDA.get_segment(arg)
        if seg is not None:
            return seg.perm # RWX e.g. 0b101 = R + X
        return None

    @staticmethod
    def get_segment_type(arg):
        seg = IDA.get_segment(arg)
        if seg is not None:
            return seg.type # 0x1 SEG_DATA 0x2 SEG_CODE See doc for details
        return None

    @staticmethod
    def get_instruction(addr):
        r = ida_ua.print_insn_mnem(addr)
        if r == "":
            return None
        return r

    # immidiate value
    @staticmethod
    def get_operand(addr, n):
        return (idc.get_operand_type(addr, n), idc.get_operand_value(addr, n))

    # eax, ecx, etc
    @staticmethod
    def print_operand(addr, n):
        return idc.print_operand(addr, n)

    @staticmethod
    def get_instruction_size(addr):
        return ida_bytes.get_item_size(addr)

    @staticmethod
    def get_instructions_count(begin, end):
        p = begin
        cnt = 0
        while p < end:
            sz = IDA.get_instruction_size(p)
            cnt += 1
            p += sz
        return cnt

    @staticmethod
    def get_name(addr):
        return ida_name.get_name(addr)

    @staticmethod
    def get_name_address(name, addr=0):
        return ida_name.get_name_ea(addr, name)

    @staticmethod
    def get_bytes(addr, l):
        return ida_bytes.get_bytes(addr, l)

    @staticmethod
    def get_byte(addr):
        return ida_bytes.get_byte(addr)

    @staticmethod
    def get_word(addr):
        return ida_bytes.get_word(addr)

    @staticmethod
    def get_dword(addr):
        return ida_bytes.get_dword(addr)

    @staticmethod
    def get_qword(addr):
        return ida_bytes.get_qword(addr)

    @staticmethod
    def get_xrefsto(addr, flags=ida_xref.XREF_ALL):
        return [ref.frm for ref in idautils.XrefsTo(addr, flags)]

    @staticmethod
    def get_xrefsfrom(addr, flags=ida_xref.XREF_ALL):
        return [ref.frm for ref in idautils.XrefsFrom(addr, flags)]

    @staticmethod
    def get_input_file_path():
        return ida_nalt.get_input_file_path()

    @staticmethod
    def get_info_structure():
        return ida_idaapi.get_inf_structure()

    @staticmethod
    def get_main_address():
        return IDA.get_info_structure().main

    @staticmethod
    def get_max_address():
        return IDA.get_info_structure().max_ea

    @staticmethod
    def get_min_address():
        return IDA.get_info_structure().min_ea

    @staticmethod
    def is_big_endian():
        return IDA.get_info_structure().is_be()

    @staticmethod
    def is_little_endian():
        return not IDA.is_big_endian()

    @staticmethod
    def get_filetype():
        info = IDA.get_info_structure()
        ftype = info.filetype
        if ftype == ida_ida.f_MACHO:
            return "macho"
        elif ftype == ida_ida.f_PE or ftype == ida_ida.f_EXE or ftype == ida_ida.f_EXE_old: # is this correct?
            return "pe"
        elif ftype == ida_ida.f_ELF:
            return "elf"
        else:
            return None

    @staticmethod
    def get_ql_arch_string():
        info = IDA.get_info_structure()
        proc = info.procname.lower()
        result = None
        if proc == "metapc":
            result = "x86"
            if info.is_64bit():
                result = "x8664"
        elif "mips" in proc:
            result = "mips"
        elif "arm" in proc:
            result = "arm32"
            if info.is_64bit():
                result = "arm64"
        # That's all we support :(
        return result

    @staticmethod
    def get_current_address():
        return ida_kernwin.get_screen_ea()

    # return (?, start, end)
    @staticmethod
    def get_last_selection():
        return ida_kernwin.read_range_selection(None)

    # Use with skipcalls
    # note that the address is the end of target instruction
    # e.g.:
    # 0x1 push eax
    # 0x4 mov eax, 0
    # call get_frame_sp_delta(0x4) and get -4.
    @staticmethod
    def get_frame_sp_delta(addr):
        return ida_frame.get_sp_delta(IDA.get_function(addr), addr)

    @staticmethod
    def patch_bytes(addr, bs):
        return ida_bytes.patch_bytes(addr, bs)

    @staticmethod
    def fill_bytes(start, end, bs = b'\x90'):
        return ida_bytes.patch_bytes(start, bs*(end-start))

    @staticmethod
    def nop_selection():
        _, start, end = IDA.get_last_selection()
        return IDA.fill_bytes(start, end)

    @staticmethod
    def fill_block(bb, bs=b'\x90'):
        return IDA.fill_bytes(bb.start_ea, bb.end_ea, bs)

    @staticmethod
    def assemble(ea, cs, ip, use32, line):
        return ida_idp.assemble(ea, cs, ip, use32, line)

    @staticmethod
    def create_data(ea, dataflag, size, tid=ida_netnode.BADNODE):
        return ida_bytes.create_data(ea, dataflag, size, tid)

    @staticmethod
    def create_bytes_array(start, end):
        return IDA.create_data(start, ida_bytes.byte_flag(), end-start)

    @staticmethod
    def create_byte(ea, length, force=False):
        return ida_bytes.create_byte(ea, length, force)

    @staticmethod
    def perform_analysis(start, end, final_pass=True):
        return ida_auto.plan_and_wait(start, end)

    @staticmethod
    def get_item_head(ea):
        return ida_bytes.get_item_head(ea)

    @staticmethod
    def get_item_end(ea):
        return ida_bytes.get_item_end(ea)

    @staticmethod
    def get_item_size(ea):
        return ida_bytes.get_item_size(ea)

    @staticmethod
    def get_item(ea):
        return (IDA.get_item_head(ea), IDA.get_item_end(ea))

    @staticmethod
    def is_colored_item(ea):
        return ida_nalt.is_colored_item(ea)

    # NOTE: The [start, end) range should include all control flows except long calls.
    @staticmethod
    def get_micro_code_mba(start, end, decomp_flags=ida_hexrays.DECOMP_WARNINGS, maturity=7):
        mbrgs = ida_hexrays.mba_ranges_t()
        rg = ida_range.range_t(start, end)
        mbrgs.ranges.push_back(rg)
        fl = ida_hexrays.hexrays_failure_t()
        mba = ida_hexrays.gen_microcode(mbrgs, fl, None, decomp_flags, maturity)
        if mba is None:
            logging.error(f"Fail to get mba because: {fl}")
        return mba

    @staticmethod
    def micro_code_from_mbb(mbb):
        cur = mbb.head
        while cur is not None:
            yield cur
            cur = cur.next
        return

### View Class

class QlEmuRegView(simplecustviewer_t):
    def __init__(self, ql_emu_plugin):
        super(QlEmuRegView, self).__init__()
        self.hooks = None
        self.ql_emu_plugin = ql_emu_plugin

    def Create(self):
        title = "QL Register View"
        if not simplecustviewer_t.Create(self, title):
            return False

        self.menu_update = 1

        class Hooks(UI_Hooks):
            class PopupActionHandler(action_handler_t):
                def __init__(self, subview, menu_id):
                    action_handler_t.__init__(self)
                    self.subview = subview
                    self.menu_id = menu_id

                def activate(self, ctx):
                    self.subview.OnPopupMenu(self.menu_id)

                def update(self, ctx):
                    return AST_ENABLE_ALWAYS

            def __init__(self, form):
                UI_Hooks.__init__(self)
                self.form = form

            def finish_populating_widget_popup(self, widget, popup):
                if self.form.title == get_widget_title(widget):
                    attach_dynamic_action_to_popup(widget, popup, action_desc_t(None, "Edit Register", self.PopupActionHandler(self.form, self.form.menu_update),  None, None, -1))

        if self.hooks is None:
            self.hooks = Hooks(self)
            self.hooks.hook()

        return True

    def SetReg(self, addr, ql:Qiling):
        #clear
        self.ClearLines()

        view_title = COLSTR("Reg value at { ", SCOLOR_AUTOCMT)
        view_title += COLSTR("IDA Address:0x%X | QL Address:0x%X" % (addr - self.ql_emu_plugin.qlemu.baseaddr + get_imagebase(), addr), SCOLOR_DREF)
        # TODO: Add disass should be better
        view_title += COLSTR(" }", SCOLOR_AUTOCMT)
        self.AddLine(view_title)
        self.AddLine("")

        reglist = QlEmuMisc.get_reg_map(ql)
        line = ""
        cols = 3
        reglist = [reglist[i:i+cols] for i in range(0,len(reglist),cols)]
        arch = ql.arch.type
        for regs in reglist:
            for reg in regs:
                line += COLSTR(" %4s: " % str(reg), SCOLOR_REG)
                regvalue = ql.arch.regs.read(reg)
                if arch in [QL_ARCH.X8664, QL_ARCH.ARM64]:
                    value_format = "0x%.16X"
                else:
                    value_format = "0x%.8X"
                line += COLSTR(str(value_format % regvalue), SCOLOR_NUMBER)
                # TODO: ljust will looks better
            self.AddLine(line)
            line = ''
        self.AddLine(line)
        self.Refresh()

    def OnPopupMenu(self, menu_id):
        if menu_id == self.menu_update:
            self.ql_emu_plugin.ql_chang_reg()

    def OnClose(self):
        if self.hooks:
            self.hooks.unhook()
            self.hooks = None
        self.ql_emu_plugin.ql_close_reg_view()

class QlEmuStackView(simplecustviewer_t):
    def __init__(self, ql_emu_plugin):
        super(QlEmuStackView, self).__init__()
        self.ql_emu_plugin = ql_emu_plugin

    def Create(self):
        title = "QL Stack View"
        if not simplecustviewer_t.Create(self, title):
            return False
        return True

    def SetStack(self, ql:Qiling):
        self.ClearLines()
        if ql is None:
            return

        sp = ql.arch.regs.arch_sp
        self.AddLine('')
        self.AddLine(COLSTR('  Stack at 0x%X' % sp, SCOLOR_AUTOCMT))
        self.AddLine('')

        reg_bit_size = ql.arch.bits
        reg_byte_size = reg_bit_size // 8
        value_format = '% .16X' if reg_bit_size == 64 else '% .8X'

        for i in range(-30, 30):
            clr = SCOLOR_DREF if i < 0 else SCOLOR_INSN
            cur_addr = (sp + i * reg_byte_size)
            line = ('  ' + value_format + ': ') % cur_addr
            try:
                value = ql.mem.read(cur_addr, reg_byte_size)
                value, = struct.unpack('Q' if reg_bit_size == 64 else 'I', value)
                line += value_format % value
            except Exception:
                line += '?' * reg_byte_size * 2

            self.AddLine(COLSTR(line, clr))

    def OnClose(self):
        self.ql_emu_plugin.ql_close_stack_view()

class QlEmuMemView(simplecustviewer_t):
    def __init__(self, ql_emu_plugin, addr, size):
        super(QlEmuMemView, self).__init__()
        self.ql_emu_plugin = ql_emu_plugin
        self.viewid = addr
        self.addr = addr
        self.size = size
        self.lastContent = []

    def Create(self, title):
        if not simplecustviewer_t.Create(self, title):
            return False
        return True

    def SetMem(self, ql:Qiling):
        self.ClearLines()

        if ql is None:
            return

        try:
            memory = ql.mem.read(self.addr, self.size)
        except:
            return

        size = len(memory)

        view_title = COLSTR("  Memory at [ ", SCOLOR_AUTOCMT)
        view_title += COLSTR("0x%X: %d byte(s)" % (self.addr, size), SCOLOR_DREF)
        view_title += COLSTR(" ]", SCOLOR_AUTOCMT)
        self.AddLine(str(view_title))
        self.AddLine("")
        self.AddLine(COLSTR("                0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F", SCOLOR_AUTOCMT))

        startAddress = self.addr
        line = ""
        chars = ""
        get_char = lambda byte: chr(byte) if 0x20 <= byte <= 0x7E else '.'

        if size != 0:
            for x in range(size):
                if x%16==0:
                    line += COLSTR(" %.12X: " % startAddress, SCOLOR_AUTOCMT)
                if len(self.lastContent) == len(memory):
                    if memory[x] != self.lastContent[x]:
                        line += COLSTR(str("%.2X " % memory[x]), SCOLOR_VOIDOP)
                        chars += COLSTR(get_char(memory[x]), SCOLOR_VOIDOP)
                    else:
                        line += COLSTR(str("%.2X " % memory[x]), SCOLOR_NUMBER)
                        chars += COLSTR(get_char(memory[x]), SCOLOR_NUMBER)
                else:
                    line += COLSTR(str("%.2X " % memory[x]), SCOLOR_NUMBER)
                    chars += COLSTR(get_char(memory[x]), SCOLOR_NUMBER)

                if (x+1)%16==0:
                    line += "  " + chars
                    self.AddLine(line)
                    startAddress += 16
                    line = ""
                    chars = ""

            # add padding
            tail = 16 - size%16
            if tail != 0:
                for x in range(tail): line += "   "
                line += "  " + chars
                self.AddLine(line)

        self.Refresh()
        self.lastContent = memory

    def OnClose(self):
        self.ql_emu_plugin.ql_close_mem_view(self.viewid)

### Dialog Class
class QlEmuMemDialog(Form):
    def __init__(self):
        Form.__init__(self, r"""STARTITEM {id:mem_addr}
BUTTON YES* Add
BUTTON CANCEL Cancel
Show Memory Range
Specify start address and size of new memory range.
<##Address\::{mem_addr}> <##Size\::{mem_size}>
<##Comment\::{mem_cmnt}>
""", {
        'mem_addr': Form.NumericInput(swidth=20, tp=Form.FT_HEX),
        'mem_size': Form.NumericInput(swidth=10, tp=Form.FT_DEC),
        'mem_cmnt': Form.StringInput(swidth=41)
    })

class QlEmuSetupDialog(Form):
    def __init__(self):
        Form.__init__(self, r"""STARTITEM {id:path_name}
BUTTON YES* Start
BUTTON CANCEL Cancel
Setup Qiling
<#Select Rootfs to open#Rootfs path\:        :{path_name}>
<#Custom script path   #Custom script path\: :{script_name}>
<#Custom parameter   #Custom parameter\: :{parameters}>
<#Custom env   #Custom env\: :{env_var}>
""", {
            'path_name': Form.DirInput(swidth=50),
            'script_name': Form.FileInput(swidth=50, open=True),
            'parameters': Form.StringInput(swidth=50),
            'env_var': Form.FileInput(swidth=70, open=True),
        })

class QlEmuSaveDialog(Form):
    def __init__(self):
        Form.__init__(self, r"""STARTITEM {id:path_name}
BUTTON YES* Save
BUTTON CANCEL Cancel
Save Path
<#Save to#Path\::{path_name}>
""", {
        'path_name': Form.FileInput(swidth=50, save=True),
    })

class QlEmuLoadDialog(Form):
    def __init__(self):
        Form.__init__(self, r"""STARTITEM {id:file_name}
BUTTON YES* Load
BUTTON CANCEL Cancel
Load File
<#Load From#File\::{file_name}>
""", {
        'file_name': Form.FileInput(swidth=50, open=True)
    })

class QlEmuAboutDialog(Form):
    def __init__(self, version):
        super(QlEmuAboutDialog, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Qiling Website
Qiling:: About
            {FormChangeCb}
            Qiling IDA plugin v%s with Qiling Framework v%s.
            Author:
            Ziqiao Kong,
            Chenxu Wu,
            Qiling Team.
            Qiling is released under the GPL v2.
            Find more info at https://www.qiling.io
            """ %(version, QLVERSION), {
            '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(QilingHomePage, new = 2)

        return 1

class QlEmuUpdateDialog(Form):
    def __init__(self, version, message):
        super(QlEmuUpdateDialog, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Qiling Website
Qiling:: Check for update
            {FormChangeCb}
            Your Qiling 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(QilingHomePage, new = 2)

        return 1

class QlEmuRegEditDialog(Form):
    def __init__(self, regName):
        Form.__init__(self, r"""STARTITEM {id:reg_val}
BUTTON YES* Save
BUTTON CANCEL Cancel
Register Value
{reg_label}
<##:{reg_val}>
""", {
        'reg_label': Form.StringLabel("Edit [ " + regName + " ] value"),
        'reg_val': Form.NumericInput(tp=Form.FT_HEX, swidth=20)
        })

class QlEmuRegDialog(Choose):
    def __init__(self, reglist, flags=0, width=None, height=None, embedded=False):
        Choose.__init__(
            self, "QL Register Edit",
            [ ["Register", 10 | Choose.CHCOL_PLAIN],
              ["Value", 30] ])
        self.popup_names = ["", "", "Edit Value", ""]
        self.items = reglist

    def show(self):
        return self.Show(True) >= 0

    def OnEditLine(self, n):
        edit_dlg = QlEmuRegEditDialog(self.items[n][0])
        edit_dlg.Compile()
        edit_dlg.reg_val.value = self.items[n][1]
        ok = edit_dlg.Execute()
        if ok == 1:
            newvalue = edit_dlg.reg_val.value
            self.items[n][1] = int("%X" % newvalue, 16)
        self.Refresh()

    def OnGetLine(self, n):
        if self.items[n][2] == 32:
            return [ self.items[n][0], "0x%08X" % self.items[n][1] ]
        if self.items[n][2] == 64:
            return [ self.items[n][0], "0x%16X" % self.items[n][1] ]

    def OnGetSize(self):
        return len(self.items)

    def OnClose(self):
        pass

### Misc
class QlEmuMisc:
    MenuItem = collections.namedtuple("MenuItem", ["action", "handler", "title", "tooltip", "shortcut", "popup"])
    class menu_action_handler(action_handler_t):
        def __init__(self, handler, action):
            action_handler_t.__init__(self)
            self.action_handler = handler
            self.action_type = action

        def activate(self, ctx):
            if ctx.widget_type == BWN_DISASM:
                self.action_handler.ql_handle_menu_action(self.action_type)
            return 1

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

    @staticmethod
    def get_reg_map(ql:Qiling):
        tables = {
            QL_ARCH.X86     : list({**x86_reg_map_32, **x86_reg_map_misc, **x86_reg_map_st}.keys()),
            QL_ARCH.X8664   : list({**x86_reg_map_64, **x86_reg_map_misc, **x86_reg_map_st}.keys()),
            QL_ARCH.ARM     : list({**arm_reg_map}.keys()),
            QL_ARCH.ARM64   : list({**arm64_reg_map}.keys()),
            QL_ARCH.MIPS    : list({**mips_reg_map}.keys()),
        }

        return tables.get(ql.arch.type, [])

    @staticmethod
    def url_download(url):
        try:
            from urllib2 import Request, urlopen, URLError, HTTPError
        except:
            from urllib.request import Request, urlopen
            from urllib.error import 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 as e:
            # print "HTTP Error:", e.code , url
            # fail to download this file
            return (1, None)
        except URLError as 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)

    class QLStdIO(ql_file):
        def __init__(self, path, fd):
            super().__init__(path, fd)
            self.__fd = fd

        def write(self, write_buf):
            super().write(write_buf)
            msg(write_buf.decode('utf-8'))

        def flush(self):
            pass

        def isatty(self):
            return False

### Qiling wrapper

class QlEmuQiling:
    def __init__(self):
        self.path = None
        self.rootfs = None
        self.ql: Qiling = None
        self.status = None
        self.exit_addr = None
        self.baseaddr = None
        self.env = {}

    def start(self, *args, **kwargs):
        # ida replaces sys.stderr with their own customized class that is not fully compatible with the
        # standard stream protocol. here we patch stderr replacement to make it look like a proper file.
        # this has to happen before Qiling init
        if not hasattr(sys.stderr, 'fileno'):
            setattr(sys.stderr, 'fileno', lambda: sys.__stderr__.fileno())

        self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, *args, **kwargs)

        if sys.platform != 'win32':
            self.ql.os.stdin = QlEmuMisc.QLStdIO('stdin', sys.__stdin__.fileno())
            self.ql.os.stdout = QlEmuMisc.QLStdIO('stdout', sys.__stdout__.fileno())
            self.ql.os.stderr = QlEmuMisc.QLStdIO('stderr', sys.__stderr__.fileno())

        self.exit_addr = self.ql.os.exit_point
        if self.ql.os.type == QL_OS.LINUX:
            f = open(self.ql.path, 'rb')
            elffile = ELFFile(f)
            elf_header = elffile.header
            if elf_header['e_type'] == 'ET_EXEC':
                self.baseaddr = self.ql.loader.images[0].base
            elif elf_header['e_type'] == 'ET_DYN':
                if self.ql.arch.bits == 32:
                    self.baseaddr = int(self.ql.os.profile.get("OS32", "load_address"), 16)
                elif self.ql.arch.bits == 64:
                    self.baseaddr = int(self.ql.os.profile.get("OS64", "load_address"), 16)
        else:
            self.baseaddr = get_imagebase()

    def run(self, begin=None, end=None):
        self.ql.run(begin, end)

    def set_reg(self):
        reglist = QlEmuMisc.get_reg_map(self.ql)
        regs = [ [ row, int(self.ql.arch.regs.read(row)), self.ql.arch.bits ] for row in reglist ]
        regs_len = len(regs)
        RegDig = QlEmuRegDialog(regs)
        if RegDig.show():
            for idx, val in enumerate(RegDig.items[0:regs_len-1]):
                self.ql.arch.regs.write(reglist[idx], val[1])
            return True
        else:
            return False

    def save(self):
        savedlg = QlEmuSaveDialog()
        savedlg.Compile()

        if savedlg.Execute() != 1:
            return False

        savepath = savedlg.path_name.value

        self.ql.save(reg=True, mem=True,fd=True, cpu_context=True, snapshot=savepath)
        logging.info('Save to ' + savepath)
        return True

    def load(self):
        loaddlg = QlEmuLoadDialog()
        loaddlg.Compile()

        if loaddlg.Execute() != 1:
            return False

        loadname = loaddlg.file_name.value

        self.ql.restore(snapshot=loadname)
        logging.info('Restore from ' + loadname)
        return True

    def remove_ql(self):
        if self.ql is not None:
            del self.ql
            self.ql = None

    def ql_addr_from_ida(self, addr):
        return addr - get_imagebase() + self.baseaddr

    def ida_addr_from_ql_addr(self, addr):
        return addr - self.baseaddr + get_imagebase()

### Plugin

class QlEmuPlugin(plugin_t, UI_Hooks):
    ### Ida Plugin Data

    popup_menu_hook = None

    flags = PLUGIN_HIDE
    comment = ""

    help = "Qiling Emulator"
    wanted_name = "Qiling Emulator"
    wanted_hotkey = ""

    ### View Data

    qlemuregview = None
    qlemustackview = None
    qlemumemview = {}

    def __init__(self):
        super(QlEmuPlugin, self).__init__()
        self.plugin_name = "Qiling Emulator"
        self.qlemu = None
        self.ql = None
        self.stepflag = True
        self.stephook = None
        self.qlinit = False
        self.lastaddr = None
        self.is_change_addr = -1
        self.userobj = None
        self.customscriptpath = None
        self.bb_mapping = {}
        self.ks = None

    ### Main Framework

    def init(self):
        # init data
        logging.info('---------------------------------------------------------------------------------------')
        logging.info('Qiling Emulator Plugin For IDA, by Qiling Team. Version {0}, 2020'.format(QLVERSION))
        logging.info('Based on Qiling v{0}'.format(QLVERSION))
        logging.info('Find more information about Qiling at https://qiling.io')
        logging.info('---------------------------------------------------------------------------------------')
        self.qlemu = QlEmuQiling()
        self.ql_hook_ui_actions()
        return PLUGIN_KEEP

    def run(self, arg = 0):
        logging.info(f"Registering actions.")
        self.ql_register_menu_actions()
        self.ql_attach_main_menu_actions()

    def ready_to_run(self):
        logging.info(f"UI is ready, register our menu actions.")
        self.run()

    def term(self):
        self.qlemu.remove_ql()
        self.ql_unhook_ui_actions()
        self.ql_detach_main_menu_actions()
        self.ql_unregister_menu_actions()

    ### Actions

    def ql_start(self):
        if self.qlemu is None:
            self.qlemu = QlEmuQiling()
        if self.ql_set_rootfs():
            logging.info(f'Rootfs: {self.qlemu.rootfs}')
            logging.info(f"Custom user script: {self.customscriptpath}")
            logging.info(f"Custom env: {self.qlemu.env}")
            show_wait_box("Qiling is processing ...")
            try:
                self.qlemu.start()
                self.qlinit = True
                self.lastaddr = None
            finally:
                hide_wait_box()
                logging.info("Qiling is initialized successfully.")
        if self.customscriptpath is not None:
            self.ql_load_user_script()
            self.userobj.custom_prepare(self.qlemu.ql)

        ida_ida.ql_plugin = self

    def ql_load_user_script(self):
        if self.qlinit :
            self.ql_get_user_script(is_reload=True, is_start=True)
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_reload_user_script(self):
        if self.qlinit:
            self.ql_get_user_script(is_reload=True)
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_continue(self):
        if self.qlinit:
            userhook = None
            pathhook = self.qlemu.ql.hook_code(self.ql_path_hook)
            if self.userobj is not None:
                userhook = self.userobj.custom_continue(self.qlemu.ql)
            if self.qlemu.status is not None:
                self.qlemu.ql.restore(self.qlemu.status)
                show_wait_box("Qiling is processing ...")
                try:
                    self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=self.qlemu.exit_addr)
                finally:
                    hide_wait_box()
            else:
                show_wait_box("Qiling is processing ...")
                try:
                    self.qlemu.run()
                finally:
                    hide_wait_box()
            self.qlemu.ql.hook_del(pathhook)
            if userhook and userhook is not None:
                for hook in userhook:
                    self.qlemu.ql.hook_del(hook)
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
        else:
            logging.error('Qiling should be setup firstly.')

    def _color_path(self, color):
        def _cb(ql, addr, size):
            real_addr = addr - self.qlemu.baseaddr + get_imagebase()
            set_color(real_addr, CIC_ITEM, color)
        return _cb

    def ql_run_selection(self):
        if self.qlinit:
            _, start, end = IDA.get_last_selection()
            # Drop all previous status
            self.qlemu.status = None
            if self.userobj is not None:
                userhook = self.userobj.custom_execute_selection(self.qlemu.ql)
            colorhook = self.qlemu.ql.hook_code(self._color_path(Colors.Pink.value))
            show_wait_box("Qiling is processing ...")
            try:
                self.qlemu.run(begin=start, end=end)
            finally:
                hide_wait_box()
            self.qlemu.ql.hook_del(colorhook)
            if userhook and userhook is not None:
                for hook in userhook:
                    self.qlemu.ql.hook_del(hook)
            self.qlemu.status = self.qlemu.ql.save()
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_set_pc(self):
        if self.qlinit:
            ea = self.qlemu.ql_addr_from_ida(IDA.get_current_address())
            self.qlemu.ql.arch.regs.arch_pc = ea
            logging.info(f"QIling PC set to {hex(ea)}")
            self.qlemu.status = self.qlemu.ql.save()
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_run_to_here(self):
        if self.qlinit:
            curr_addr = get_screen_ea()
            untillhook = self.qlemu.ql.hook_code(self.ql_untill_hook)
            if self.qlemu.status is not None:
                self.qlemu.ql.restore(self.qlemu.status)
                show_wait_box("Qiling is processing ...")
                try:
                    self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=curr_addr+self.qlemu.baseaddr-get_imagebase())
                finally:
                    hide_wait_box()
            else:
                show_wait_box("Qiling is processing ...")
                try:
                    self.qlemu.run(end=curr_addr+self.qlemu.baseaddr-get_imagebase())
                finally:
                    hide_wait_box()

            set_color(curr_addr, CIC_ITEM, 0x00B3CBFF)
            self.qlemu.ql.hook_del(untillhook)
            self.qlemu.status = self.qlemu.ql.save()
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_step(self):
        if self.qlinit:
            userhook = None
            self.stepflag = True
            self.qlemu.ql.restore(saved_states=self.qlemu.status)
            self.stephook = self.qlemu.ql.hook_code(callback=self.ql_step_hook)
            if self.userobj is not None:
                userhook = self.userobj.custom_step(self.qlemu.ql)
            self.qlemu.run(begin=self.qlemu.ql.arch.regs.arch_pc, end=self.qlemu.exit_addr)
            if userhook and userhook is not None:
                for hook in userhook:
                    self.qlemu.ql.hook_del(hook)
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_save(self):
        if self.qlinit:
            if self.qlemu.save() != True:
                logging.error('Fail to save the snapshot.')
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_load(self):
        if self.qlinit:
            if self.qlemu.load() != True:
                logging.error('Fail to load the snapshot.')
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_chang_reg(self):
        if self.qlinit:
            self.qlemu.set_reg()
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
            self.qlemu.status = self.qlemu.ql.save()
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_reset(self):
        if self.qlinit:
            self.ql_close()
            self.qlemu = QlEmuQiling()
            self.ql_start()
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_close(self):
        if self.qlinit:
            heads = Heads(get_segm_start(get_screen_ea()), get_segm_end(get_screen_ea()))
            for i in heads:
                set_color(i, CIC_ITEM, 0xFFFFFF)
            self.qlemu.remove_ql()
            del self.qlemu
            self.qlemu = None
            self.qlinit = False
            logging.info('Qiling is deleted.')
        else:
            logging.error('Qiling is not started.')

    def ql_show_reg_view(self):
        if self.qlinit:
            if self.qlemuregview is None:
                self.qlemuregview = QlEmuRegView(self)
                QlEmuRegView(self)
                self.qlemuregview.Create()
                self.qlemuregview.SetReg(self.qlemu.ql.arch.regs.arch_pc, self.qlemu.ql)
                self.qlemuregview.Show()
                self.qlemuregview.Refresh()
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_show_stack_view(self):
        if self.qlinit:
            if self.qlemustackview is None:
                self.qlemustackview = QlEmuStackView(self)
                self.qlemustackview.Create()
                self.qlemustackview.SetStack(self.qlemu.ql)
                self.qlemustackview.Show()
                self.qlemustackview.Refresh()
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_show_mem_view(self, addr=get_screen_ea(), size=0x10):
        if self.qlinit:
            memdialog = QlEmuMemDialog()
            memdialog.Compile()
            memdialog.mem_addr.value = addr
            memdialog.mem_size.value = size
            ok = memdialog.Execute()
            if ok == 1:
                mem_addr = memdialog.mem_addr.value - self.qlemu.baseaddr + get_imagebase()
                mem_size = memdialog.mem_size.value
                mem_cmnt = memdialog.mem_cmnt.value

                if mem_addr not in self.qlemumemview:
                    if not self.qlemu.ql.mem.is_mapped(mem_addr, mem_size):
                        ok = ask_yn(1, "Memory [%X:%X] is not mapped!\nDo you want to map it?\n   YES - Load Binary\n   NO - Fill page with zeroes\n   Cancel - Close dialog" % (mem_addr, mem_addr + mem_size))
                        if ok == 0:
                            self.qlemu.ql.mem.map(mem_addr, mem_size)
                            self.qlemu.ql.mem.write(mem_addr, b"\x00"*mem_size)
                        elif ok == 1:
                            # TODO: map_binary
                            return
                        else:
                            return
                    self.qlemumemview[mem_addr] = QlEmuMemView(self, mem_addr, mem_size)
                    if mem_cmnt == []:
                        self.qlemumemview[mem_addr].Create("QL Memory")
                    else:
                        self.qlemumemview[mem_addr].Create("QL Memory [ " + mem_cmnt + " ]")
                    self.qlemumemview[mem_addr].SetMem(self.qlemu.ql)
                self.qlemumemview[mem_addr].Show()
                self.qlemumemview[mem_addr].Refresh()
        else:
            logging.error('Qiling should be setup firstly.')

    def ql_unload_plugin(self):
        heads = Heads(get_segm_start(get_screen_ea()), get_segm_end(get_screen_ea()))
        for i in heads:
            set_color(i, CIC_ITEM, 0xFFFFFF)
        self.ql_close()
        self.ql_detach_main_menu_actions()
        self.ql_unregister_menu_actions()
        logging.info('Unload plugin successfully!')

    def ql_menu_null(self):
        pass

    def ql_about(self):
        self.aboutdlg = QlEmuAboutDialog(QLVERSION)
        self.aboutdlg.Execute()
        self.aboutdlg.Free()

    def ql_check_update(self):
        (r, content) = QlEmuMisc.url_download(QilingStableVersionURL)
        content = content.decode("utf-8")
        if r == 0:
            try:
                version_stable = re.findall(r"\"([\d\.]+)\"", content)[0]
            except (TypeError, IndexError):
                warning("ERROR: Failed to find the Qiling version string from response.")
                logging.warning("Failed to find the Qiling version string from response.")

            # compare with the current version
            if version_stable == QLVERSION:
                self.updatedlg = QlEmuUpdateDialog(QLVERSION, "Good, you are already on the latest stable version!")
                self.updatedlg.Execute()
                self.updatedlg.Free()
            else:
                self.updatedlg = QlEmuUpdateDialog(QLVERSION, "Download latest stable version {0} from https://github.com/qilingframework/qiling/blob/master/qiling/extensions/idaplugin".format(version_stable))
                self.updatedlg.Execute()
                self.updatedlg.Free()
        else:
            # fail to download
            warning("ERROR: Failed to connect to Github. Try again later.")
            logging.warning("Failed to connect to Github when checking for the latest update. Try again later.")

    def _remove_from_bb_lists(self, bbid):
        if bbid in self.real_blocks:
            self.real_blocks.remove(bbid)
        elif bbid in self.fake_blocks:
            self.fake_blocks.remove(bbid)
        elif bbid in self.retn_blocks:
            self.retn_blocks.remove(bbid)

    def ql_mark_real(self):
        if len(self.bb_mapping) == 0:
            logging.error(f"Please perform auto analysis before marking blocks manually!")
            return
        cur_addr = IDA.get_current_address()
        cur_block = IDA.get_block(cur_addr)
        self._remove_from_bb_lists(cur_block.id)
        self.real_blocks.append(cur_block.id)
        IDA.color_block(cur_block, Colors.Green.value)

    def ql_mark_fake(self):
        if len(self.bb_mapping) == 0:
            logging.error(f"Please perform auto analysis before marking blocks manually!")
            return
        cur_addr = IDA.get_current_address()
        cur_block = IDA.get_block(cur_addr)
        self._remove_from_bb_lists(cur_block.id)
        self.fake_blocks.append(cur_block.id)
        IDA.color_block(cur_block, Colors.Gray.value)

    def ql_mark_retn(self):
        if len(self.bb_mapping) == 0:
            logging.error(f"Please perform auto analysis before marking blocks manually!")
            return
        cur_addr = IDA.get_current_address()
        cur_block = IDA.get_block(cur_addr)
        self._remove_from_bb_lists(cur_block.id)
        self.retn_blocks.append(cur_block.id)
        IDA.color_block(cur_block, Colors.Pink.value)

    def __in_bb(self, addr, bb):
        return addr < bb.end_ea and addr >= bb.start_ea

    # Identify if the given microcode block is a jmp_mbb.
    # Core patterns:
    #     ...
    #     ...
    #     goto @dispatcher
    def __is_jmp_mbb(self, mbb):
        ins_list = list(IDA.micro_code_from_mbb(mbb))
        if len(ins_list) == 0:
            logging.warning(f"Get an empty mbb at {hex(mbb.start)}?!")
            return False
        last_ins = ins_list[-1]
        if last_ins.opcode != ida_hexrays.m_goto:
            logging.warning(f"jmp_mbb at {hex(mbb.start)} the opcode of last instruction {last_ins._print()} isn't goto")
            return False
        if last_ins.l.t != ida_hexrays.mop_b:
            logging.warning(f"jmp_mbb at {hex(mbb.start)} the l of last instruction {last_ins._print()} doesn't have a microcode block reference")
            return False
        goto_mbb = self.mbbs[last_ins.l.b]
        mbb_start = goto_mbb.start
        dispatcher_bb = self.bb_mapping[self.dispatcher]
        pre_dispatcher_bb = self.bb_mapping[self.pre_dispatcher]
        if self.__in_bb(mbb_start, dispatcher_bb) or self.__in_bb(mbb_start, pre_dispatcher_bb):
            return True
        logging.warning(f"The address {hex(mbb_start)} where jmp_mbb goes isn't pre_dispatcher or dispatcher block!")
        return False

    # Identify if the given microcode block is a next_mbb.
    # Core patterns:
    #     mov #imm, reg
    def __is_next_mbb(self, mbb):
        ins_list = list(IDA.micro_code_from_mbb(mbb))
        if len(ins_list) == 0:
            logging.warning(f"Get an empty mbb at {hex(mbb.start)}?!")
            return False
        first_ins = ins_list[0]
        if first_ins.opcode != ida_hexrays.m_mov:
            logging.warning(f"next_mbb at {hex(mbb.start)} the opcode of first instruction {first_ins._print()} isn't mov")
            return False
        if first_ins.l.t != ida_hexrays.mop_n:
            logging.warning(f"next_mbb at {hex(mbb.start)} the l of first instruction {first_ins._print()} isn't an immediate number")
            return False
        if first_ins.d.t != ida_hexrays.mop_r:
            logging.warning(f"next_mbb at {hex(mbb.start)} the d of first instruction {first_ins._print()} isn't a reg")
            return False
        if len(ins_list) == 1:
            logging.info(f"A block with only one instruction which is `mov #imm, reg` at {hex(mbb.start)}.")
        return True

    def _get_jmp_ins(self, ida_addr, insns):
        # This cloud really happen! See issue #804. TODO: Investigate or re-design insns structure or replace it with ESIL.
        # So we have to fallback to legacy path.
        if ida_addr not in insns:
            return (None, None)
        ins_list = insns[ida_addr]
        result = []
        for bbid, ins in ins_list:
            if ida_hexrays.is_mcode_jcond(ins.opcode):
                result.append((bbid, ins))
        if len(result) > 1:
            logging.warning(f"More than one conditional jmp detected at {hex(ida_addr)}!")
        elif len(result) == 0:
            logging.warning(f"No conditional jmp found at {hex(ida_addr)}!")
            return (None, None)
        return result[0]

    # cmov/it eq:
    #   < ... condiontal jump > --> < mov imm, reg > (next_bb) --> < dispatcher >
    #                           |-> < dispatcher > (jmp_mbb)
    # NOTE:
    #   LLVM IR -> ASM -> IDA IR
    #   I guess this pattern is stable enough for us to rely on.
    def _force_execution_with_microcode(self, ql, ida_addr):
        _, insns, mbbs = self._prepare_microcodes(maturity=7)
        bbid, ins = self._get_jmp_ins(ida_addr, insns)
        if ins is None:
            return False
        # According to comments in hexrays.hpp, it may be a mop_v. I guess that shouldn't exist
        # so we add a sanity check here.
        if ins.d.t != ida_hexrays.mop_b:
            logging.warning(f"Sanity check: microcode {ins._print()} doesn't refer a block!")
        jmp_mbb = mbbs[ins.d.b]
        next_mbb = mbbs[bbid].nextb
        if not (self.__is_next_mbb(next_mbb) and self.__is_jmp_mbb(jmp_mbb)):
            # Switch the branch and try again?
            logging.info("Switch the jmp_bb and next_bb and try again...")
            if self.__is_jmp_mbb(next_mbb) and self.__is_next_mbb(jmp_mbb):
                jmp_mbb, next_mbb = next_mbb, jmp_mbb
            else:
                logging.error(f"Fail to identify microcode blocks at {hex(ida_addr)}")
                return False
        ins_list = list(IDA.micro_code_from_mbb(next_mbb))
        first_ins = ins_list[0]
        imm = first_ins.l.nnn.value
        reg_name = ida_hexrays.get_mreg_name(first_ins.d.r, ql.arch.pointersize)
        logging.info(f"Froce set {reg_name} to {hex(imm)}")
        ql.arch.regs.__setattr__(reg_name, imm)
        return True

    def _ida_address_after_branch(self, ida_addr):
        _, ins = self._get_jmp_ins(ida_addr, self.insns)
        if ins is None:
            return None
        return self.mbbs[ins.d.b].start

    # legacy approach for x86_64 and arm64
    # TODO & NOTE: We still need this fallback since the microcode pattern we match now sometimes may change. A better
    #              approach is that we fully drop pattern machting and emulates the microcode while recording the correspoding
    #              branch condition.
    #              For example:
    #                CSEL            W8, W8, W9, LT
    #                =>
    #                'LT' -> W9->W8 (the value of W9 can be get from emulation)
    #                'GT' -> W8->W8
    #                We assume that the comparison (like cmp W10, 0) is the same between real assembly and microcode and the only
    #                difference is the condition of the jump instruction. So the 'LT' and 'GT' here represent condition both in microcode
    #                and original assembly.
    #              For current implementation we don't record 'LT' or 'GT' which makes it hard to patch code after force execution.
    def _force_execution_by_parsing_assembly(self, ql, ida_addr):
        if "x86" in IDA.get_ql_arch_string(): # cmovlg eax, ebx
            reg1 = IDA.print_operand(ida_addr, 0).lower()
            reg2 = IDA.print_operand(ida_addr, 1).lower()
            reg2_val = ql.arch.regs.__getattr__(reg2)
            logging.info(f"Force set {reg1} to {hex(reg2_val)}")
            ql.arch.regs.__setattr__(reg1, reg2_val)
            return True
        elif "arm" in IDA.get_ql_arch_string():
            instr = IDA.get_instruction(ida_addr).lower()
            logging.info(f"Going to force execute: {instr}")
            if instr.startswith("it"): # itt eq\n moveqw low\n movteq high\n
                ida_addr = ida_addr + IDA.get_instruction_size(ida_addr)
                low = IDA.get_operand(ida_addr, 1)
                ida_addr = ida_addr + IDA.get_instruction_size(ida_addr)
                high = IDA.get_operand(ida_addr, 1)
                reg = IDA.print_operand(ida_addr, 0).lower()
                val = (high << 16) + low
                logging.info(f"Force set {reg} to {hex(val)}")
                ql.arch.regs.__setattr__(reg, val)
                return True
            elif "csel" in instr: # csel dst, src1, src2, cond
                dst = IDA.print_operand(ida_addr, 0).lower()
                src = IDA.print_operand(ida_addr, 2).lower()
                src_val = ql.arch.regs.__getattr__(src)
                logging.info(f"Force set {dst} to {hex(src_val)}")
                ql.arch.regs.__setattr__(dst, src_val)
                return True
        return False

    def _has_call_insn(self, ida_addr):
        if ida_addr not in self.insns:
            return False
        ins_list = self.insns[ida_addr]
        for _, ins in ins_list:
            if ida_hexrays.is_mcode_call(ins.opcode):
                return True
        return False

    def _guide_hook(self, ql, addr, size):
        start_bb_id = self.hook_data['startbb']
        ida_addr = self.deflatqlemu.ida_addr_from_ql_addr(addr)
        func = self.hook_data['func']
        if ida_addr < func.start_ea or ida_addr >= func.end_ea:
            logging.error(f"Address {hex(ida_addr)} out of function boundaries!")
            ql.emu_stop()
            self.hook_data['result'] = False
            return
        cur_bb = IDA.get_block(ida_addr)
        if "force" in self.hook_data and ida_addr in self.hook_data['force']:
            if self.hook_data['force'][ida_addr]:
                logging.info(f"Force execution at {hex(ida_addr)}")
                result = self._force_execution_with_microcode(ql, ida_addr)
                if not result:
                    logging.warning(f"Fail to force execution by microcode at {hex(ida_addr)}, trying legacy approach")
                    result = self._force_execution_by_parsing_assembly(ql, ida_addr)
                    if not result:
                        logging.error(f"Fail to force execution by legacy approach at {hex(ida_addr)}, stop now...")
                        self.hook_data['result'] = False
                        ql.emu_stop()
                        return
                    self.hook_data['force_legacy'] = True
            else:
                pass
            if not self.hook_data['force_legacy']:
                next_ida_addr = self._ida_address_after_branch(ida_addr)
            else:
                next_ida_addr = ida_addr + IDA.get_instruction_size(ida_addr)
            logging.info(f"Goto {hex(next_ida_addr)} after branch...")
            ql.arch.regs.arch_pc = self.deflatqlemu.ql_addr_from_ida(next_ida_addr) + self.append
            ida_addr = next_ida_addr
        # TODO: Maybe we can detect whether the program will access unmapped
        #       here so that we won't map the memory.
        if self._has_call_insn(ida_addr):
            ql.arch.regs.arch_pc += IDA.get_instruction_size(ida_addr) + self.append
            return
        if start_bb_id == cur_bb.id:
            return
        if cur_bb.id in self.real_blocks or cur_bb.id in self.retn_blocks:
            if cur_bb.id not in self.paths[start_bb_id]:
                self.paths[start_bb_id].append(cur_bb.id)
            ql.emu_stop()

    def _skip_unmapped_rw(self, ql: Qiling, type, addr, size, value):
        map_addr = ql.mem.align(addr)
        map_size = ql.mem.align_up(size)

        if not ql.mem.is_mapped(map_addr, map_size):
            logging.warning(f"Invalid memory R/W, trying to map {hex(map_size)} at {hex(map_addr)}")

            ql.mem.map(map_addr, map_size)
            ql.mem.write(map_addr, b'\x00' * map_size)

        return True

    def _find_branch_in_real_block(self, bb):
        #paddr = bb.start_ea
        #while paddr < bb.end_ea:
        #    ins = IDA.get_instruction(paddr)
        #    sz = IDA.get_instruction_size(paddr)
        #    if ins.lower().startswith("cmov"):
        #        return paddr
        #    paddr += sz

        # microcode implementation
        #  Is it possible for the address of a microcode instruction not equal
        #  to any start address of a real instruction? IDK, only Hexrays knows.
        #  So we iterate all addresses so that we won't miss any posssible branchs.
        for addr in range(bb.start_ea, bb.end_ea):
            if addr in self.insns:
                for _, insn in self.insns[addr]:
                    opcode = insn.opcode
                    if ida_hexrays.is_mcode_jcond(opcode):
                        return addr
        return None

    def _log_paths_str(self):
        for bbid, succs in self.paths.items():
            if len(succs) == 1:
                logging.info(f"{self._block_str(bbid)} -> {self._block_str(succs[0])}")
            elif len(succs) == 2:
                logging.info(f"{self._block_str(bbid)} --(force jump)--> {self._block_str(succs[0])}")
                logging.info(f"|----(skip jump)----> {self._block_str(succs[1])}")
            elif len(succs) > 2:
                logging.warning(f"succs: {succs} found from {self._block_str(bbid)}!")

    # Is this correct?
    def _thumb_detect(self, ida_addr):
        return IDA.get_instruction_size(ida_addr) == 2

    def _log_verbose(self, ql, addr, size):
        logging.debug(f"addr: {hex(addr)} ida_addr: {hex(self.deflatqlemu.ida_addr_from_ql_addr(addr))}")
        registers = [ k for k in ql.arch.regs.register_mapping.keys() if type(k) is str ]
        for idx in range(0, len(registers), 3):
            regs = registers[idx:idx+3]
            s = "\t".join(map(lambda v: f"{v:4}: {ql.arch.regs.__getattr__(v):016x}", regs))
            logging.debug(s)

    # Q: Why we need emulation to help us find real control flow considering there are some
    #    switch-case patterns in mircocode which can be analysed statically?
    # A: Emulation makes the de-obf much more robust and general and can work under less assumptions, like
    #    the optimization level, the complexity of original control flow or even the verson of the
    #    Hexrays decompiler because we try to avoid matching fixed microcode patterns. In fact, static
    #    analysis is always possible since the brain of a reverse engineer is the best decompiler
    #    and emulator, isn't it? :D
    def _search_path(self):
        self.paths = {bbid: [] for bbid in self.bb_mapping.keys()}
        reals = [self.first_block, *self.real_blocks]
        self.deflatqlemu = QlEmuQiling()
        self.deflatqlemu.path = self.qlemu.path
        self.deflatqlemu.rootfs = self.qlemu.rootfs
        first_block = self.bb_mapping[self.first_block]
        if IDA.get_ql_arch_string() == "arm32":
            if self._thumb_detect(first_block.start_ea):
                logging.info(f"Thumb detected, enable it.")
                self.deflatqlemu.start(archtype=QL_ARCH.ARM, thumb=True)
                self.deflatqlemu.ql.arch.regs.cpsr |= 0x20
                self.append = 1
        else:
            self.deflatqlemu.start()
            self.append = 0
        ql = self.deflatqlemu.ql
        if logging.root.level <= logging.DEBUG:
            ql.hook_code(self._log_verbose)
        self.hook_data = None
        ql.hook_mem_read_invalid(self._skip_unmapped_rw)
        ql.hook_mem_write_invalid(self._skip_unmapped_rw)
        ql.hook_mem_unmapped(self._skip_unmapped_rw)
        # set up stack before we really run.
        ql.run(begin=self.deflatqlemu.ql_addr_from_ida(first_block.start_ea) + self.append, end=self.deflatqlemu.ql_addr_from_ida(first_block.end_ea), count=0xFFF)
        # okay, we can set up our core hook now.
        ql.hook_code(self._guide_hook)
        for bbid in reals:
            logging.debug(f"Search control flow for block: {self._block_str(bbid)}")
            bb = self.bb_mapping[bbid]
            braddr = self._find_branch_in_real_block(bb)
            self.hook_data = {
                "startbb": bbid,
                "func": IDA.get_function(first_block.start_ea),
                "result": True,
                "force_legacy": False
            }
            ql_bb_start_ea = self.deflatqlemu.ql_addr_from_ida(bb.start_ea) + self.append
            ctx = ql.save()
            # Skip force execution in the first block.
            # `end=0` is a workaround for ql remembering last exit_point.
            if braddr is None or bb.id == self.first_block:
                ql.run(begin=ql_bb_start_ea, end=0, count=0xFFF)
            else:
                self.hook_data['force'] = {braddr: True}
                ctx2 = ql.save()
                ql.run(begin=ql_bb_start_ea, end=0, count=0xFFF)
                ql.restore(ctx2)
                if not self.hook_data['result']:
                    return False
                self.hook_data['force'] = {braddr: False}
                ql.run(begin=ql_bb_start_ea, end=0, count=0xFFF)
            ql.restore(ctx)
            if not self.hook_data['result']:
                return False
        self._log_paths_str()
        return True


    # IDA doesn't support arm assembling, so it's a good chance to replace IDA
    # assembler implmentation with keystone.
    def _initialize_keystone(self):
        if self.ks is None:
            self.ks = self.deflatqlemu.ql.arch.assembler


    def _asm(self, *args, **kwargs):
        self._initialize_keystone()
        return self.ks.asm(*args, **kwargs)

    def _assemble(self, instr, addr):
        logging.debug(f"Keystone: Assemble {instr} at {hex(addr)}")
        bs, count = self._asm(instr, addr)
        IDA.patch_bytes(addr, bytes(bs))
        IDA.perform_analysis(addr, addr + len(bs))
        return bs, count

    # Patching microcode is TOO complex.
    # I would rahter write another 1e10 llvm passes than a single hexrays decompiler pass.
    def _arch_jmp_instruction(self, addr):
        arch = IDA.get_ql_arch_string()
        op = None
        if "x86" in arch:
            op = "jmp"
        elif "arm" in arch:
            op = "B"
        elif "mips" in arch:
            op = "j"
        return f"{op} {addr}"

    # See comments above.
    def _arch_cond_jmp_instruction(self, cond, addr):
        arch = IDA.get_ql_arch_string()
        op = None
        if "x86" in arch:
            op = f"j{cond}"
        elif "arm" in arch:
            op = f"b{cond}"
        elif "mips" in arch:
            op = f"j{cond}"
        return f"{op} {addr}"

    # See comments above.
    def _arch_parse_cond_from_addr(self, braddr):
        arch = IDA.get_ql_arch_string()
        instr = IDA.get_instruction(braddr).lower()
        if "x86" in arch: # cmovge
            return instr[4:]
        elif "arm" in arch:
            if instr.startswith("it"): # itt eq
                tks = instr.split(" ")
                if len(tks) != 2:
                    logging.error(f"Can't get condition from {instr}")
                    return None
                return tks[-1]
            elif "csel" in instr:
                return IDA.print_operand(braddr, 3)
        # TODO: mips
        return None

    # Really FORCE ida to analyse again.
    def _force_analysis(self, start, end):
        ida_bytes.del_items(start, 0, end-start)
        ida_auto.plan_and_wait(start, end)
        return

    # The only way to make IDA generate right assembly is to undefine bytes patched just now
    # and call IDA to analyse it again. Nice job again as always, Hexrays!
    def _patch_bytes_with_force_analysis(self, start, bs):
        IDA.patch_bytes(start, bs)
        return self._force_analysis(start, start+len(bs))

    def _arch_branch_patch(self, braddr, bbid):
        bb = self.bb_mapping[bbid]
        force_addr = self.bb_mapping[self.paths[bbid][0]].start_ea
        normal_addr = self.bb_mapping[self.paths[bbid][1]].start_ea
        # Temporary dirty fix.
        # See comments for _force_execution_by_parsing_assembly.
        if "arm64" == IDA.get_ql_arch_string():
            force_addr, normal_addr = normal_addr, force_addr
        # Parse condition before patching nop.
        cond = self._arch_parse_cond_from_addr(braddr)
        buffer = [0] * (bb.end_ea - braddr)
        instr_to_assemble = self._arch_cond_jmp_instruction(cond, f"{hex(force_addr)}h")
        logging.info(f"Assemble {instr_to_assemble} at {hex(force_addr)}")
        bs1, _ = self._asm(instr_to_assemble, braddr)
        buffer[:len(bs1)] = bs1
        next_instr_address = braddr + len(bs1)
        instr_to_assemble = self._arch_jmp_instruction(f"{hex(normal_addr)}h")
        logging.info(f"Assemble {instr_to_assemble} at {hex(normal_addr)}")
        bs2, _ = self._asm(instr_to_assemble, next_instr_address)
        buffer[len(bs1):len(bs1) + len(bs2)] = bs2
        logging.info(f"Patch real block with branch from {hex(braddr)} to {hex(bb.end_ea)}")
        self._patch_bytes_with_force_analysis(braddr, bytes(buffer))


    def _patch_codes(self):
        if len(self.paths[self.first_block]) != 1:
            logging.error(f"Found wrong ways in first block: {self._block_str(self.bb_mapping[self.first_block])}, should be 1 path but get {len(self.paths[self.first_block])}, exit.")
            return
        logging.info("NOP dispatcher block")
        dispatcher_bb = self.bb_mapping[self.dispatcher]
        # Some notes:
        #    Patching b'\x00' instead of 'nop' can help IDA decompile a better result. Don't know why...
        #    Besides
        buffer = [0] * (dispatcher_bb.end_ea - dispatcher_bb.start_ea)
        first_jmp_addr = dispatcher_bb.start_ea
        instr_to_assemble = self._arch_jmp_instruction(f"{hex(self.bb_mapping[self.paths[self.first_block][0]].start_ea)}h")
        logging.info(f"Assemble {instr_to_assemble} at {hex(first_jmp_addr)}")
        bs, _ = self._asm(instr_to_assemble, first_jmp_addr)
        buffer[:len(bs)] = bs
        logging.info(f"Patch first jump at {hex(first_jmp_addr)}")
        self._patch_bytes_with_force_analysis(first_jmp_addr, bytes(buffer))
        for bbid in self.real_blocks:
            logging.debug(f"Patching real block: {self._block_str(bbid)}")
            bb = self.bb_mapping[bbid]
            braddr = self._find_branch_in_real_block(bb)
            if braddr is None:
                last_instr_address = IDA.get_prev_head(bb.end_ea)
                buffer = [0x90] * (bb.end_ea - last_instr_address)
                if len(self.paths[bbid]) != 1:
                    logging.warning(f"Found wrong ways in block: {self._block_str(bb)}, should be 1 path but get {len(self.paths[bbid])}")
                    continue
                instr_to_assemble = self._arch_jmp_instruction(f"{hex(self.bb_mapping[self.paths[bbid][0]].start_ea)}h")
                logging.info(f"Assemble {instr_to_assemble} at {hex(last_instr_address)}")
                bs, _ = self._asm(instr_to_assemble, last_instr_address)
                buffer[:len(bs)] = bs
                logging.info(f"Patch real block from {hex(last_instr_address)} to {hex(bb.end_ea)}")
                self._patch_bytes_with_force_analysis(last_instr_address, bytes(buffer))
            else:
                if len(self.paths[bbid]) != 2:
                    logging.warning(f"Found wrong ways in block: {self._block_str(bb)}, should be 2 paths but get {len(self.paths[bbid])}")
                    continue
                self._arch_branch_patch(braddr, bbid)
        for bbid in self.fake_blocks:
            bb = self.bb_mapping[bbid]
            logging.info(f"Patch NOP for block: {self._block_str(bb)}")
            self._patch_bytes_with_force_analysis(bb.start_ea, b"\x00"*(bb.end_ea-bb.start_ea))
        logging.info(f"Patch NOP for pre_dispatcher.")
        bb = self.bb_mapping[self.pre_dispatcher]
        self._patch_bytes_with_force_analysis(bb.start_ea, b"\x00"*(bb.end_ea-bb.start_ea))

    def _prepare_microcodes(self, decomp_flags=ida_hexrays.DECOMP_WARNINGS | ida_hexrays.DECOMP_NO_WAIT, maturity=7):
        dispatcher_bb = self.bb_mapping[self.dispatcher]
        target_function = IDA.get_function(dispatcher_bb.start_ea)
        # Reduce optimization to make pattern more stable.
        logging.info(f"Generate microcode from {hex(target_function.start_ea)} to {hex(target_function.end_ea)}")
        mba = IDA.get_micro_code_mba(target_function.start_ea, target_function.end_ea, decomp_flags, maturity)
        insns = {}
        mbbs = {}
        for i in range(mba.qty):
            mbb = mba.get_mblock(i)
            mbbs[i] = mbb
            cur_insn = mbb.head
            while cur_insn != None:
                insn_ea = cur_insn.ea
                if insn_ea not in insns:
                    insns[insn_ea] = []
                insns[insn_ea].append((i, cur_insn))
                cur_insn = cur_insn.next
        return mba, insns, mbbs

    def ql_deflat(self):
        if len(self.bb_mapping) == 0:
            self.ql_parse_blocks_for_deobf()
        if not self.qlinit:
            logging.info("Qiling should be setup firstly!")
            return
        self.mba, self.insns, self.mbbs = self._prepare_microcodes(maturity=3)
        logging.debug("Microcode generation done. Going to search path.")
        if not self._search_path():
            logging.info(f"Fail to search path. Please fire an issue to us at https://github.com/qilingframework/qiling with relevant logs!")
            return
        logging.debug("Real control flows search done. Going to patch codes.")
        self._patch_codes()
        logging.debug("Codes patched. Let's tell IDA to analyse the whole function again.")
        IDA.perform_analysis(self.deflat_func.start_ea, self.deflat_func.end_ea)
        del self.deflatqlemu
        self.deflatqlemu = None
        self.ks = None

    def _block_str(self, bb):
        if type(bb) is int:
            bb = self.bb_mapping[bb]
        return f"Block id: {bb.id}, start_address: {bb.start_ea:x}, end_address: {bb.end_ea:x}, type: {bb.type}"

    def ql_parse_blocks_for_deobf(self):
        cur_addr = IDA.get_current_address()
        flowchart = IDA.get_flowchart(cur_addr)
        self.deflat_func = IDA.get_function(cur_addr)
        self.bb_mapping = {bb.id:bb for bb in flowchart}
        if flowchart is None:
            return
        bb_count = {}
        for bb in flowchart:
            for succ in bb.succs():
                if succ.id not in bb_count:
                    bb_count[succ.id] = 0
                bb_count[succ.id] += 1
        max_ref_bb_id = None
        max_ref = 0
        for bb_id, ref in bb_count.items():
            if ref > max_ref:
                max_ref = ref
                max_ref_bb_id = bb_id
        self.pre_dispatcher = max_ref_bb_id
        try:
            self.dispatcher = list(self.bb_mapping[self.pre_dispatcher].succs())[0].id
            self.first_block = flowchart[0].id
        except IndexError:
            logging.error("Fail to get dispatcher and first_block.")
            return
        self.real_blocks = []
        self.fake_blocks = []
        self.retn_blocks = []
        for bb in flowchart:
            if self.pre_dispatcher in [b.id for b in bb.succs()] and IDA.get_instructions_count(bb.start_ea, bb.end_ea) > 1:
                self.real_blocks.append(bb.id)
            elif IDA.block_is_terminating(bb):
                self.retn_blocks.append(bb.id)
            elif bb.id != self.first_block and bb.id != self.pre_dispatcher and bb.id != self.dispatcher:
                self.fake_blocks.append(bb.id)
        for bbid in self.real_blocks:
            IDA.color_block(self.bb_mapping[bbid], Colors.Green.value)
        for bbid in self.fake_blocks:
            IDA.color_block(self.bb_mapping[bbid], Colors.Gray.value)
        for bbid in self.retn_blocks:
            IDA.color_block(self.bb_mapping[bbid], Colors.Pink.value)
        IDA.color_block(self.bb_mapping[self.dispatcher], Colors.Blue.value)
        IDA.color_block(self.bb_mapping[self.pre_dispatcher], Colors.Blue.value)
        IDA.color_block(self.bb_mapping[self.first_block], Colors.Beige.value)
        logging.info(f"First block: {self._block_str(self.first_block)}")
        logging.info(f"Dispatcher: {self._block_str(self.dispatcher)}")
        logging.info(f"Pre dispatcher: {self._block_str(self.pre_dispatcher)}")
        logging.info(f"Real blocks:")
        for s in map(self._block_str, self.real_blocks): logging.info(s)
        logging.info(f"Fake blocks:")
        for s in map(self._block_str, self.fake_blocks): logging.info(s)
        logging.info(f"Return blocks:")
        for s in map(self._block_str, self.retn_blocks): logging.info(s)
        logging.info(f"Auto analysis finished, please check whether the result is correct.")
        logging.info(f"You may change the property of each block manually if necessary.")

    # jb addr
    # jnb addr
    # <=>
    # jmp addr / nop
    # nop
    def _junk_useless_jcc(self, start, end):
        bs = IDA.get_bytes(start, end-start)
        patterns = []
        # Volume 2 Table A-2
        for opc in range(0x70, 0x7F, 2):
            # jb -> jnb
            r_opc = opc + 1
            patterns.append( re.compile(re.escape(bytes([opc])) + rb'.' + re.escape(bytes([r_opc])) + rb'.') )
            patterns.append( re.compile(re.escape(bytes([r_opc])) + rb'.' + re.escape(bytes([opc])) + rb'.') )

        for pattern in patterns:
            logging.debug(f"Start with pattern: {pattern}")
            tmpbs = bs
            result = re.search(pattern, tmpbs)
            while result is not None:
                l = result.span()[0]
                r = result.span()[1]
                l_offset = tmpbs[l+1]
                r_offset = tmpbs[l+3]
                tmpbs = tmpbs[r:]
                result = re.search(pattern, tmpbs)
                if l_offset == r_offset + 2:
                    logging.info(f"Get a junk jcc at [{hex(start+l)}, {hex(start+r)}] with offset {hex(l_offset)}.")
                    # If it jumps down, check if we can fill the codes with nops safely.
                    if l_offset <= 0x7F:
                        can_fill_with_nops = True
                        for addr in range(start+l, start + l +  2 + l_offset):
                            if len(IDA.get_xrefsto(addr, flags=ida_xref.XREF_FAR)) != 0:
                                logging.info(f"Find multiple Xrefs at {hex(addr)}, patch a jmp...")
                                can_fill_with_nops = False
                                break
                        if can_fill_with_nops:
                            logging.info(f"Fill NOPs from {hex(start+l)} to {hex(start + l + 2 + l_offset)}.")
                            IDA.fill_bytes(start+l, start + l + 2 + l_offset)
                            continue
                    # Or we simply patch a jmp.
                    logging.info(f"Patch a jmp at {start+l:x}.")
                    IDA.fill_bytes(start+l, start+r)
                    IDA.patch_bytes(start+l, b"\xeb" + bytes([l_offset]))


    # Remove junk code by fixed patterns.
    # If you find new patterns, please fire an issue or PR!
    # TODO: Find some samples to add more patterns.
    def ql_remove_junk_code_by_patterns(self):
        _, start, end = IDA.get_last_selection()
        self._junk_useless_jcc(start, end)


    def ql_nop_items_without_colors(self):
        _, start, end = IDA.get_last_selection()
        cur_start, cur_end = IDA.get_item(start)
        while cur_start < end:
            if not IDA.is_colored_item(cur_start):
                logging.info(f"Item at [{hex(cur_start)}, {hex(cur_end)}) doesn't have a color, nop it.")
                IDA.fill_bytes(cur_start, cur_end)
            cur_start, cur_end = IDA.get_item(cur_end)

    ### Hook

    def ql_step_hook(self, ql, addr, size):
        self.stepflag = not self.stepflag
        addr = addr - self.qlemu.baseaddr + get_imagebase()
        if self.stepflag:
            set_color(addr, CIC_ITEM, 0x00FFD700)
            self.ql_update_views(self.qlemu.ql.arch.regs.arch_pc, ql)
            self.qlemu.status = ql.save()
            ql.os.stop()
            self.qlemu.ql.hook_del(self.stephook)
            jumpto(addr)

    def ql_path_hook(self, ql, addr, size):
        addr = addr - self.qlemu.baseaddr + get_imagebase()
        set_color(addr, CIC_ITEM, 0x007FFFAA)
        bp_count = get_bpt_qty()
        bp_list = []
        if bp_count > 0:
            for num in range(0, bp_count):
                bp_list.append(get_bpt_ea(num))

            if addr in bp_list and (addr != self.lastaddr or self.is_change_addr>1):
                self.qlemu.status = ql.save()
                ql.os.stop()
                self.lastaddr = addr
                self.is_change_addr = -1
                jumpto(addr)

            self.is_change_addr += 1


    def ql_untill_hook(self, ql, addr, size):
        addr = addr - self.qlemu.baseaddr + get_imagebase()
        set_color(addr, CIC_ITEM, 0x00B3CBFF)

    ### User Scripts

    def ql_get_user_script(self, is_reload=False, is_start=False):
        def get_user_scripts_obj(scriptpath:str, classname:str, is_reload:bool):
            try:
                import os, sys
                import importlib

                modulepath,filename = os.path.split(scriptpath)
                logging.info(modulepath)
                logging.info(filename)
                scriptname,_ = os.path.splitext(filename)
                logging.info(scriptname)
                sys.path.append(modulepath)
                module = importlib.import_module(scriptname)

                if is_reload:
                    del self.userobj
                    importlib.reload(module)
                cls = getattr(module, classname)
                return cls()
            except Exception as e:
                logging.exception("")
                return None

        self.userobj = get_user_scripts_obj(self.customscriptpath, 'QILING_IDA', is_reload)
        if self.userobj is not None:
            if is_reload and not is_start:
                logging.info('Custom user script is reloaded.')
            else:
                logging.info('Custom user script is loaded successfully.')
        else:
            logging.info('Custom user script not found.')

    ### Dialog

    def ql_set_rootfs(self):
        setupdlg = QlEmuSetupDialog()
        setupdlg.Compile()

        if setupdlg.Execute() != 1:
            return False

        rootfspath = setupdlg.path_name.value
        customscript = setupdlg.script_name.value
        parameter = setupdlg.parameters.value
        env = setupdlg.env_var.value

        if customscript != '':
            self.customscriptpath = customscript

        para_array = shlex.split(parameter)
        self.qlemu.path = [get_input_file_path()] + para_array
        logging.info(self.qlemu.path)

        if env != '':
            try:
                env_f = open(env)
                self.qlemu.env = load(env_f)
                env_f.close()
            except Exception as e:
                logging.error("Error parsing the env file!!")
                logging.error(e.message)
                return False

        if self.qlemu is not None:
            self.qlemu.rootfs = rootfspath
            return True
        return False

    ### Menu

    menuitems = []

    def ql_register_new_action(self, act_name, act_text, act_handler, shortcut, tooltip, icon):
        new_action = action_desc_t(
            act_name,       # The action name. This acts like an ID and must be unique
            act_text,       # The action text.
            act_handler,    # The action handler.
            shortcut,       # Optional: the action shortcut
            tooltip,        # Optional: the action tooltip (available in menus/toolbar)
            icon)           # Optional: the action icon (shows when in menus/toolbars)
        register_action(new_action)

    def ql_handle_menu_action(self, action):
        [x.handler() for x in self.menuitems if x.action == action]

    def ql_register_menu_actions(self):
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":start",             self.ql_start,                 "Setup",                      "Setup",                     None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":reloaduserscripts", self.ql_reload_user_script,      "Reload User Scripts",        "Reload User Scripts",       None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":runtohere",         self.ql_run_to_here,             "Execute Till",               "Execute Till",              None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":runselection",      self.ql_run_selection,           "Execute Selection",         "Execute Selection",              None,                True))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":runfromhere",       self.ql_continue,              "Continue",                   "Continue",                  None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":setpc",             self.ql_set_pc,                "Set PC",                     "Set PC",              None,                True))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":step",              self.ql_step,                  "Step",                       "Step (CTRL+SHIFT+F9)",      "CTRL+SHIFT+F9",        True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":changreg",          self.ql_chang_reg,              "Edit Register",              "Edit Register",             None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":reset",             self.ql_reset,                 "Restart",                    "Restart Qiling",            None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":close",             self.ql_close,                 "Close",                      "Close Qiling",              None,                   False  ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":reg view",          self.ql_show_reg_view,           "View Register",              "View Register",             None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":stack view",        self.ql_show_stack_view,         "View Stack",                 "View Stack",                None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":memory view",       self.ql_show_mem_view,           "View Memory",                "View Memory",               None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":save",              self.ql_save,                  "Save Snapshot",              "Save Snapshot",             None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":load",              self.ql_load,                  "Load Snapshot",              "Load Snapshot",             None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        if UseAsScript:
            self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":unload",            self.ql_unload_plugin,           "Unload Plugin",              "Unload Plugin",             None,                   False  ))
            self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   False  ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":about",             self.ql_about,                 "About",                      "About",                     None,                   False  ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":checkupdate",       self.ql_check_update,           "Check Update",               "Check Update",              None,                   False  ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":parseblocks",       self.ql_parse_blocks_for_deobf,           "Auto Analysis For Deflat",               "Auto Analysis For Deflat",              None,                   True  ))
        # TODO: Dispatcher blocks should support assignment.
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":markreal",       self.ql_mark_real,           "Mark as Real Block",               "Mark as Real Block",              None,                   True  ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":markfake",       self.ql_mark_fake,           "Mark as Fake Block",               "Mark as Fake Block",              None,                   True  ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":markretn",       self.ql_mark_retn,           "Mark as Return Block",               "Mark as Return Block",              None,                   True  ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":deflat",       self.ql_deflat,           "Deflat",               "Deflat",              None,                   True  ))
        self.menuitems.append(QlEmuMisc.MenuItem("-",                                     self.ql_menu_null,              "",                           None,                        None,                   True   ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":removejunkcodebypatterns",       self.ql_remove_junk_code_by_patterns,           "Remove Junk Code by Patterns",               "Remove Junk Code by Patterns",              None,                   True  ))
        self.menuitems.append(QlEmuMisc.MenuItem(self.plugin_name + ":nopinstructions",       self.ql_nop_items_without_colors,           "Nop Items without Color",               "Nop Items without Color",              None,                   True  ))

        for item in self.menuitems:
            if item.action == "-":
                continue
            self.ql_register_new_action(item.action, item.title, QlEmuMisc.menu_action_handler(self, item.action), item.shortcut, item.tooltip,  -1)

    def ql_unregister_menu_actions(self):
        for item in self.menuitems:
            unregister_action(item.action)

    def ql_attach_main_menu_actions(self):
        for item in self.menuitems:
            attach_action_to_menu("Edit/Plugins/" + self.plugin_name + "/" + item.title, item.action, SETMENU_APP)

    def ql_detach_main_menu_actions(self):
        for item in self.menuitems:
            detach_action_from_menu("Edit/Plugins/" + self.plugin_name + "/" + item.title, item.action)

    ### POPUP MENU

    def ql_hook_ui_actions(self):
        self.popup_menu_hook = self
        self.popup_menu_hook.hook()

    def ql_unhook_ui_actions(self):
        if self.popup_menu_hook != None:
            self.popup_menu_hook.unhook()

    # IDA 7.x

    def finish_populating_widget_popup(self, widget, popup_handle):
        if get_widget_type(widget) == BWN_DISASM:
            for item in self.menuitems:
                if item.popup:
                    attach_action_to_popup(widget, popup_handle, item.action, self.plugin_name + "/")

    ### Close View

    def ql_close_reg_view(self):
        self.qlemuregview = None

    def ql_close_stack_view(self):
        self.qlemustackview = None

    def ql_close_mem_view(self, viewid):
        del self.qlemumemview[viewid]

    def ql_close_all_views(self):
        if self.qlemuregview is not None:
            self.qlemuregview.Close()
        if self.qlemustackview is not None:
            self.qlemustackview.Close()

        for viewid in self.qlemumemview:
            self.qlemumemview[viewid].Close()
            self.qlemumemview = None

    def ql_update_views(self, addr, ql):
        if self.qlemuregview is not None:
            self.qlemuregview.SetReg(addr, ql)

        if self.qlemustackview is not None:
            self.qlemustackview.SetStack(self.qlemu.ql)

        for id in self.qlemumemview:
            self.qlemumemview[id].SetMem(self.qlemu.ql)


def PLUGIN_ENTRY():
    qlEmu = QlEmuPlugin()
    return qlEmu

if UseAsScript:
    if __name__ == "__main__":
        qlEmu = QlEmuPlugin()
        qlEmu.init()
        qlEmu.run()


repository: matthewrhannon/IDAPython-Automation-Scripts
file: pyyysigme.py
wanted_name: pySigMaker
comment: Creates code signature patterns.
created_at: 2024-09-24 12:53:57+00:00
pushed_at: 2024-09-24 13:32:30+00:00
url:
https://github.com/matthewrhannon/IDAPython-Automation-Scripts/blob/cad9d3a5bb5a92ac65736b674a7166348a7ebcc6/pyyysigme.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 = "Z:/IDA-Pro-7.7-main/plugins"
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'
        self.__configName = '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 list(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 list(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

addresses = {0x1439123E0,0x144058D27,0x1440432C0,0x144058DBC,0x144044438,0x1443C9E38,0x144058D25,0x140D7F810,0x14274F030,0x141C46400,0x142769B40,0x140E3CCD0,0x142749CB0,0X142769730,0x142749B70,0x1443C8948,0x142749D50,0x14274F5F0,0x140B39210,0x144058DC0,0x140B3D110,0x144058D3C,0x140B3E0A0,0x140B50060,0x140B3CFB0,0x140B3CEE0,0x144045708,0x140B50010,0x1440456F8,0x142769990,0x1440456F8,0x140D2C700,0x1443C8131,0x1443C83E1,0x1443C8531,0x1443C84C1,0x1443C8361,0x1443C8451,0x1443C85A1,0x1443C8051,0x1443C8131,0x143A897D8,0x143A0AB20,0x140218360,0x1443C82D8,0x140D0D040,0x14276EAC0,0x14276D190,0x140D0FD60,0x140C6AA40,0x1443C7DA0,0x1443C7E90,0x1443C7E10,0x1443C7CC0,0x1443C7C58,0x1443C7C50,0x1443C82F1,0x142766A40,0x142748B90,0x14274F240,0x140B483F0,0x1431BA740,0x1431B97D0,0x1458E0490,0x140D2A970,0x144058D38,0x145A920C0,0x143A0BFC1,0x140858200,0x140D12690,0x140D209F0,0x14068C0E0,0x1406580B0,0x144058DBC,0x1440432C0}
counter0 = -1

def run():
    global counter0
    #funcs = idautils.Functions()
    #funcs = idautils.Names()
    sigger = sigmaker_t()
    sigger.run()
    for func in addresses:
        ea1 = func #func.here()
        now = ea1
        
        #print('[+] CurPos: ' + hex(now))
        #cur_func = idc.get_name_ea_simple(0, idc.get_func_name(func))
        #cur_func = idc.get_name_ea_simple(name) #(func)

        ida_kernwin.jumpto(ea1)
        #print('[+] CurFuncName: ' + str(name)) #str(idc.get_func_name(func)))
        #print('   [+]CurPos: ' + hex(now))
        #print('   [+] CurFunc: ' + hex(cur_func))
        func_start = idc.get_func_attr(now, idc.FUNCATTR_START)
        func_end = idc.get_func_attr(now, idc.FUNCATTR_END)
        #print('   [+] FuncStart: ' + hex(func_start))
        #print('   [+] FuncEnd: ' + hex(func_end))

        counter0 = counter0 + 1
        print(("Addresses[" + str(counter0) + "]=" + hex(func)))
        
        print((gsigmaker.Gui._sigAtCursor()))
        ida_kernwin.jumpto(ea1)
        print((gsigmaker.Gui._sigCurrentFunction()))
        #print(str(sig()))


repository: gistable/gistable
file: all-gists/b59679e31dbdb01064d6bceb827ed88b/snippet.py
description: GitHub gists and the dockerfiles needed to run them.
wanted_name: HexJump
comment: Easily follow DWORD addresses within hex dump
created_at: 2018-02-01 16:19:24+00:00
pushed_at: 2023-02-11 18:20:10+00:00
url:
https://github.com/gistable/gistable/blob/665d39a2bd82543d5196555f0801ef8fd4a3ee48/all-gists/b59679e31dbdb01064d6bceb827ed88b/snippet.py

content:
import idaapi
import idc


class HexJumpHandler(idaapi.action_handler_t):
    def activate(self, ctx):
        selection = idaapi.read_selection()
        valid_selection = selection[0]
        if (valid_selection):
            addr = idc.DbgDword(selection[1])
            idaapi.jumpto(addr)
        else:
            idaapi.msg("Invalid selection!\n")

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


class HexJumpHooks(idaapi.UI_Hooks):
    def finish_populating_tform_popup(self, form, popup):
        if idaapi.get_tform_type(form) == idaapi.BWN_DUMP:
            desc = idaapi.action_desc_t(None, 'Follow in hex dump', HexJumpHandler())
            idaapi.attach_dynamic_action_to_popup(form, popup, desc, None)


class HexJump(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = "Easily follow DWORD addresses within hex dump"
    help = "Highlight DWORD address within hex dump and right-click 'Follow in hex dump'"
    wanted_name = "HexJump"
    wanted_hotkey = ""

    def init(self):
        global hooks
        hooks = HexJumpHooks()
        re = hooks.hook()
        return idaapi.PLUGIN_OK

    def run(self, arg):
        pass

    def term(self):
        pass


def PLUGIN_ENTRY():
    return HexJump()


repository: FFT-Hackers-old/IDA-Tools
file: plugins/ida-sync.py
description: The IDA sync plugin
wanted_name: IDA-Sync
comment: JSON workspaces for IDA
created_at: 2018-07-06 21:36:24+00:00
pushed_at: 2019-01-16 05:14:52+00:00
url:
https://github.com/FFT-Hackers-old/IDA-Tools/blob/a8ba253612f05fcf6ea6f79846138bfe9a9d9482/plugins/ida-sync.py

content:
from __future__ import print_function

import os
import json

import ida_bytes
import ida_enum
import ida_kernwin
import ida_nalt
import ida_name
import ida_offset
import ida_struct
import ida_typeinf
import ida_ua

import idautils
import idc

from ida_idaapi import BADADDR

def filter_none(dict):
	newDict = {}
	for k in dict:
		if dict[k] != None:
			newDict[k] = dict[k]
	return newDict

class Settings(object):

    """Handles miscellaneous global settings."""

    KEY = 'settings'

    @staticmethod
    def dump():
        return {
            'compiler': idc.get_inf_attr(idc.INF_COMPILER).id,
        }

    @staticmethod
    def load(info):
        idc.set_inf_attr(idc.INF_COMPILER, info['compiler'])


class Functions(object):

    """Handles names given to subroutines."""

    KEY = 'functions'

    @staticmethod
    def dump():
        ret = []
        for addr in idautils.Functions():
            name = ida_name.get_name(addr)
            if name.startswith('sub_') or name.startswith('nullsub_'):
                continue

            # For some reason, this get_type function doesn't include the name,
            # but the SetType function expects it.
            typ = ida_typeinf.idc_get_type(addr)
            if typ:
                typ = typ.replace('__cdecl', '__cdecl %s' % name) + ';'

            ret.append(filter_none({
                'start': addr,
                'name': name,
                'type': typ,
            }))
        return ret

    @staticmethod
    def load(infos):
        idc.set_inf_attr(idc.INF_COMPILER, 6)
        for info in infos:
            type = info.get('type', None)
            ida_name.set_name(info['start'], info['name'])
            if type:
                idc.SetType(info['start'], type)

class Enums(object):

    """Handles enum definitions."""

    KEY = 'enums'

    @staticmethod
    def dump():
        ret = []
        for i in range(ida_enum.get_enum_qty()):
            enum_id = ida_enum.getn_enum(i)

            members = []

            class V(ida_enum.enum_member_visitor_t):

                def visit_enum_member(self, cid, value):
                    members.append({
                        'value': value,
                        'name': ida_enum.get_enum_member_name(cid),
                    })
                    return 0

            ida_enum.for_all_enum_members(enum_id, V())

            ret.append({
                'idx': ida_enum.get_enum_idx(enum_id),
                'name': ida_enum.get_enum_name(enum_id),
                'width': ida_enum.get_enum_width(enum_id),
                'flag': ida_enum.get_enum_flag(enum_id),
                'members': members,
            })

        return ret

    @staticmethod
    def load(infos):
        for info in infos:
            enum_id = ida_enum.get_enum(info['name'])
            if enum_id == BADADDR:
                print('[IDA-Sync] Creating new enum %s.' % info['name'])
                enum_id = ida_enum.add_enum(
                    info['idx'],
                    info['name'],
                    info['flag'],
                )
            else:
                ida_enum.set_enum_idx(enum_id, info['idx'])
                ida_enum.set_enum_flag(enum_id, info['flag'])

            ida_enum.set_enum_width(enum_id, info['width'])

            for member in info['members']:
                ida_enum.add_enum_member(
                    enum_id, member['name'], member['value'])


class Structs(object):

    """Handles struct definitions and uses of members as offsets to memory accesses."""

    KEY = 'structs'

    @staticmethod
    def dump():
        ret = []

        for struct_idx, struct_id, struct_name in idautils.Structs():
            struct = ida_struct.get_struc(struct_id)

            members = [{'offset': offset, 'name': name, 'size': size}
                       for offset, name, size in idautils.StructMembers(struct_id)]

            # Find all xrefs to any members of this struct.
            xrefs = []
            for offset, name, size in idautils.StructMembers(struct_id):
                member = ida_struct.get_member_by_name(struct, name)
                if member is not None:
                    for xref in idautils.XrefsTo(member.id):
                        d = {
                            'from': xref.frm,
                            'type': xref.type,
                        }

                        # Get offset base if it's an offset xref.
                        if xref.type == 1:
                            d['offset'] = ida_offset.get_offbase(xref.frm, 1)

                        xrefs.append(d)

            ret.append({
                'idx': struct_idx,
                'name': struct_name,
                'members': members,
                'xrefs': xrefs,
            })

        return ret

    @staticmethod
    def load(infos):
        insn = ida_ua.insn_t()

        for info in infos:
            # Find or create struct.
            struct_id = ida_struct.get_struc_id(info['name'])
            if struct_id == BADADDR:
                print('[IDA-Sync] Creating new struct %s.' % info['name'])
                struct_id = ida_struct.add_struc(info['idx'], info['name'])
            struct = ida_struct.get_struc(struct_id)

            ida_struct.set_struc_idx(struct, info['idx'])

            # Create struct members.
            for member in info['members']:
                ida_struct.add_struc_member(
                    struct,
                    member['name'],
                    member['offset'],
                    # flag
                    0,
                    # opinfo_t instance... maybe it should sometimes be
                    # something?
                    None,
                    member['size'],
                )

            # Create xrefs to members of the struct as offsets.
            for xref in info['xrefs']:
                typ = xref['type']

                # Offset xref.
                if typ == 1:
                    # TODO figure out what second argument does.
                    idc.op_plain_offset(xref['from'], 1, xref['offset'])

                # Read/write xrefs.
                elif typ in [2, 3]:
                    ida_ua.create_insn(xref['from'], insn)
                    idc.op_stroff(insn, 1, struct.id, 0)

                # TODO do the other cases come up?
                else:
                    pass


class Arrays(object):
    KEY = 'arrays'

    @staticmethod
    def dump():
        return []

    @staticmethod
    def load(infos):
        for info in infos:
            idc.make_array(info['start'], info['length'])


class Data(object):

    """Handles struct/array definitions and labels in memory."""

    KEY = 'data'

    @staticmethod
    def dump():
        ret = []
        for addr, name in idautils.Names():
            flags = ida_bytes.get_flags(addr)
            # The 'a' heuristic is fairly bad but we need to filter out IDA's default
            # naming for strings
            if ida_bytes.has_dummy_name(flags) or ida_bytes.has_auto_name(flags) or not ida_bytes.is_data(flags) or name[0] == 'a':
                continue

            # Sometimes the auto-generated names don't actually usually have the
            # right flags set, so skip these auto-looking names.
            if any(name.startswith(s) for s in ['byte_', 'word_', 'dword_', 'unk_', 'jpt_']):
                continue

            sz = ida_bytes.get_item_size(addr)

            if ida_bytes.is_struct(flags):
                ti = ida_nalt.opinfo_t()
                ida_bytes.get_opinfo(ti, addr, 0, flags)
                typ = ida_struct.get_struc_name(ti.tid)
            else:
                typ = None
			
            ret.append(filter_none({
                'address': addr,
                'name': name,
                'type': typ,
                'sz': sz,
                'flags': flags,
            }))

        return ret

    @staticmethod
    def load(infos):
        for info in infos:
            ida_name.set_name(info['address'], info['name'])
            type = info.get('type', None)

            # TODO this code is kind of mashed together... not sure of the
            # right way.
            tid = ida_struct.get_struc_id(type) if type else BADADDR
            if type:
                ida_bytes.create_struct(info['address'], info['sz'], tid)
            ida_bytes.create_data(
                info['address'], info['flags'], info['sz'], tid)


items = [Settings, Enums, Structs, Arrays, Data, Functions]

# We'll sometimes be passing names to IDA, which expects str, not unicode.
def convert_name_str(obj):
    for k, v in obj.items():
        if isinstance(v, unicode):
            obj[k] = v.encode('utf-8')
    return obj

class ImportHandler(ida_kernwin.action_handler_t):
	def activate(self, ctx):
		filename = ida_kernwin.ask_file(False, '*.json', 'Import IDA-Sync JSON file')
		if filename is not None:
			print('[IDA-Sync] Importing %s...' % filename)
			with open(filename) as f:
				j = json.load(f, object_hook=convert_name_str)
			for item in items:
				item.load(j.get(item.KEY, []))
			print('[IDA-Sync] Done')
		return 1

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

class ExportHandler(ida_kernwin.action_handler_t):
	def activate(self, ctx):
		filename = ida_kernwin.ask_file(True, '*.json', 'Export IDA-Sync JSON file')
		if filename is not None:
			print('[IDA-Sync] Exporting %s...' % filename)
			j = {item.KEY: item.dump() for item in items}
			with open(filename, 'w') as file:
				json.dump(j, file, indent=2, sort_keys=True, separators=(',', ': '))
			print('[IDA-Sync] Done')
		return 1

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

class IDASyncPlugin(ida_idaapi.plugin_t):
	flags = ida_idaapi.PLUGIN_UNL
	comment = "JSON workspaces for IDA"
	help = "Import/export JSON workspaces"
	wanted_name = "IDA-Sync"
	wanted_hotkey = ''

	def init(self):
		import_action = ida_kernwin.action_desc_t(
			'idasync:import',
			'IDA-Sync JSON file...',
			ImportHandler())
		export_action = ida_kernwin.action_desc_t(
			'idasync:export',
			'Create IDA-Sync JSON file...',
			ExportHandler())
		ida_kernwin.register_action(import_action)
		ida_kernwin.register_action(export_action)
		ida_kernwin.attach_action_to_menu(
			'File/Load file/Parse C header file...',
			'idasync:import',
			ida_kernwin.SETMENU_APP
		)
		ida_kernwin.attach_action_to_menu(
			'File/Produce file/Create C header file...',
			'idasync:export',
			ida_kernwin.SETMENU_APP
		)
		print("[IDA-Sync] Loaded")
		return ida_idaapi.PLUGIN_OK

	def run(self, arg):
		pass

	def term(self):
		pass

def PLUGIN_ENTRY():
	return IDASyncPlugin()


repository: Xunflash/Dejunk
file: Dejunk.py
url: https://github.com/Xunflash/Dejunk/blob/1d3918da5f0d41964c6e65540854e067acb6ad00/Dejunk.py
wanted_name: Dejunk
comment: Dejunk
created_at: 2023-01-30 11:57:14+00:00
pushed_at: 2023-09-06 06:41:55+00:00
description:
An ida plug-in that matches the bulk removal of junk instructions from the program | 批量匹配去除花指令的ida插件

content:
import re
import time
import ida_hexrays
import idaapi
import ida_idp
import idc
import ida_search
import ida_funcs
import ida_auto
import idautils
import ida_ua
import ida_kernwin
from PyQt5 import QtCore
from keystone import *
from capstone import *
from unicorn import *
from unicorn.x86_const import *
from PyQt5.QtWidgets import QSizePolicy, QPushButton, QCheckBox, QApplication, QLineEdit, QRadioButton, QTextEdit, QVBoxLayout, QLabel, QHBoxLayout, QDialog, QTabWidget, QVBoxLayout, QWidget


PLUG_NAME = "Dejunk"


def get_arch_info():
    # Get information about the binary loaded in IDA
    inf = idaapi.get_inf_structure()

    is_64bit = inf.is_64bit()

    # IDA processor names to Unicorn, Capstone, and Keystone architecture and mode constants
    mapping = {
        'metapc': {
            'uc': (UC_ARCH_X86, UC_MODE_64) if is_64bit else (UC_ARCH_X86,  UC_MODE_32),
            'cs': (CS_ARCH_X86, CS_MODE_64) if is_64bit else (CS_ARCH_X86,  CS_MODE_32),
            'ks': (KS_ARCH_X86, KS_MODE_64) if is_64bit else (KS_ARCH_X86,  KS_MODE_32),
            'nop': 0x90
        },
        'ARM': {
            'uc': (UC_ARCH_ARM64, UC_MODE_ARM) if is_64bit else (UC_ARCH_ARM, UC_MODE_ARM),
            'cs': (CS_ARCH_ARM64, CS_MODE_ARM) if is_64bit else (CS_ARCH_ARM, CS_MODE_ARM),
            'ks': (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) if is_64bit else (KS_ARCH_ARM, KS_MODE_ARM),
            'nop': 0xd503201f if is_64bit else 0xe320f000
        },
        'mipsl': {
            'uc': (UC_ARCH_MIPS, UC_MODE_MIPS32),
            'cs': (CS_ARCH_MIPS, CS_MODE_MIPS32),
            'ks': (KS_ARCH_MIPS, KS_MODE_MIPS32),
            'nop': 0x00000000
        },
        'mipsb': {
            'uc': (UC_ARCH_MIPS, UC_MODE_MIPS32 + UC_MODE_BIG_ENDIAN),
            'cs': (CS_ARCH_MIPS, CS_MODE_MIPS32 + CS_MODE_BIG_ENDIAN),
            'ks': (KS_ARCH_MIPS, KS_MODE_MIPS32 + KS_MODE_BIG_ENDIAN),
            'nop': 0x00000000
        },
        # Add more architectures here...
    }

    arch_mode_map = mapping.get(inf.procname)
    if arch_mode_map is None:
        return None, None, None

    uc_arch, uc_mode = arch_mode_map['uc']
    cs_arch, cs_mode = arch_mode_map['cs']
    ks_arch, ks_mode = arch_mode_map['ks']
    nop_inst = arch_mode_map['nop']

    return (uc_arch, uc_mode), (cs_arch, cs_mode), (ks_arch, ks_mode), nop_inst


def match_func(match: re.Match):
    hex_value = match.group(2).lower()
    if len(hex_value) == 1:
        hex_value = "0" + hex_value
    return hex_value


def match_func1(match: re.Match):
    return chr(int(match.group(2), 16))


def parse_bytes(s: str = "", match_type: int = 0):
    pattern = r"(0x|\\x)([0-9a-fA-F]{1,2})"
    if re.findall(pattern, s):
        if match_type:
            return ' '.join([match_func(m) for m in re.finditer(pattern, s)])
        else:
            return re.sub(pattern, match_func1,s)
    return s


def nop(addr, endaddr, nop_inst):
    while (addr < endaddr):
        if (nop_inst == 0x90):
            idc.patch_byte(addr, nop_inst)
            addr += 1
        else:
            idc.patch_dword(addr, nop_inst)
            addr += 4


class DejunkForm(idaapi.PluginForm):
    def __init__(self, data: str = ""):
        super().__init__()
        self.data = data

        # disable timeout for scripts
        self.old_timeout = idaapi.set_script_timeout(0)

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

    def RemoveTab(self, index):
        pass

    def PopulateForm(self):
        self.tabs = QTabWidget()
        self.tabs.setMovable(True)
        self.tabs.setTabsClosable(True)
        self.tabs.tabCloseRequested.connect(self.remove_tabs)
        self.tabs.addTab(Options(self, self.data), "Options")

        layout = QVBoxLayout()
        layout.addWidget(self.tabs)
        self.parent.setLayout(layout)

    def remove_tabs(self, index):
        if not isinstance(self.tabs.widget(index), Options):
            self.tabs.removeTab(index)

    def OnClose(self, form):
        idaapi.set_script_timeout(self.old_timeout)
        # print("[%s] Form closed." % PLUG_NAME)


class Options(QWidget):
    def __init__(self, parent, data: str = ""):
        super().__init__()
        self.parent = parent
        self.text = data
        self.name = "Options"

        uc, cs, ks, self.nop_inst = get_arch_info()
        # print("Unicorn:", uc)
        # print("Capstone:", cs)
        # print("Keystone:", ks)
        print('Nop_inst:', hex(self.nop_inst))

        # Init capstone,keystone,unicorn
        self.cs = Cs(*cs)
        self.ks = Ks(*ks)
        self.uc = Uc(*uc)

        # bytecode
        self.bytes_code = b''

        # searched func list
        self.func_list = []

        # assembly
        self.asm = b''

        self.create_gui()
        


    def create_gui(self):
        self.setWindowTitle('Dejunk')
        self.horizontalLayout_4 = QHBoxLayout()
        self.verticalLayout_2 = QVBoxLayout()
        self.matchingLayout = QHBoxLayout()
        self.matchingLayout.setContentsMargins(-1, -1, 300, -1)
        self.matchingLabel = QLabel("Matching mode")
        self.matchingLabel.setMargin(10)
        self.matchingLayout.addWidget(
            self.matchingLabel, 0, QtCore.Qt.AlignTop)
        self.option_types = [QRadioButton("All"), QRadioButton("Part")]
        self.option_types[0].setChecked(True)
        for qcheck in self.option_types:
            self.matchingLayout.addWidget(qcheck)
        self.verticalLayout_2.addLayout(self.matchingLayout)
        self.addrLayout = QHBoxLayout()
        self.addrLayout.setContentsMargins(-1, -1, 300, -1)
        self.addrLabel = QLabel("Address")
        self.addrLabel.setMargin(10)
        self.addrLayout.addWidget(
            self.addrLabel, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.startLabel = QLabel("Start:")
        self.startLabel.setMargin(10)
        self.addrLayout.addWidget(self.startLabel, 0, QtCore.Qt.AlignRight)
        seg = idaapi.get_segm_by_name(".text")
        self.startLine = QLineEdit(hex(seg.start_ea))
        self.addrLayout.addWidget(self.startLine)
        self.endLabel = QLabel("End:")
        self.endLabel.setMargin(10)
        self.addrLayout.addWidget(self.endLabel, 0, QtCore.Qt.AlignRight)
        self.endLine = QLineEdit(hex(seg.end_ea))
        self.addrLayout.addWidget(self.endLine)
        self.verticalLayout_2.addLayout(self.addrLayout)
        self.convertLayout = QHBoxLayout()  # 更改从 QVBoxLayout 到 QHBoxLayout
        self.convertLabel = QLabel("Convert")
        self.convertLabel.setMargin(10)
        self.bytesLabel = QLabel("Bytes:")
        self.bytesLabel.setMargin(10)
        self.bytesLayout = QVBoxLayout()
        self.bytesLayout.addWidget(self.bytesLabel)
        self.bytesEdit = QTextEdit()
        self.bytesEdit.focusInEvent = self.bytes_focusInEvent
        self.bytesEdit.focusOutEvent = self.bytes_focusOutEvent
        self.bytesLayout.addWidget(self.bytesEdit)
        
        if self.text:
            self.bytesEdit.setText(self.text)
        
        self.convertLayout.addLayout(self.bytesLayout)
        self.assemblyLabel = QLabel("Assembly:")
        self.assemblyLabel.setMargin(10)
        self.assemblyLayout = QVBoxLayout()
        self.assemblyLayout.addWidget(self.assemblyLabel)
        self.asmEdit = QTextEdit()
        self.asmEdit.focusInEvent = self.asm_focusInEvent
        self.asmEdit.focusOutEvent = self.asm_focusOutEvent
        self.assemblyLayout.addWidget(self.asmEdit)
        self.convertLayout.addLayout(self.assemblyLayout)
        self.verticalLayout_2.addLayout(self.convertLayout)
        self.optionLayout = QHBoxLayout()
        self.redefineCheck = QCheckBox("Auto re-define functions")
        self.optionLayout.addWidget(self.redefineCheck, 0, QtCore.Qt.AlignTop)
        self.verticalLayout_2.addLayout(self.optionLayout)
        self.applyButton = QPushButton("Apply")
        # self.testBtn = QPushButton("test")  # 测试按钮
        # self.verticalLayout_2.addWidget(self.testBtn)
        self.verticalLayout_2.addWidget(
            self.applyButton, 0, QtCore.Qt.AlignHCenter)
        self.horizontalLayout_4.addLayout(self.verticalLayout_2)
        self.bytesEdit.textChanged.connect(self.bytes_to_asm)
        self.asmEdit.textChanged.connect(self.asm_to_bytes)
        self.applyButton.clicked.connect(self.apply_clicked)
        # self.testBtn.clicked.connect(self.test)  # 测试按钮
        self.setLayout(self.horizontalLayout_4)
    
    # def test(self):
    #     pass
    
    # 获取焦点时断开连接 失去焦点时重新连接
    def bytes_focusInEvent(self, event):
        self.asmEdit.textChanged.disconnect(self.asm_to_bytes)
        super().focusInEvent(event)

    def bytes_focusOutEvent(self, event):
        self.asmEdit.textChanged.connect(self.asm_to_bytes)
        super().focusOutEvent(event)

    def asm_focusInEvent(self, event):
        self.bytesEdit.textChanged.disconnect(self.bytes_to_asm)
        super().focusInEvent(event)

    def asm_focusOutEvent(self, event):
        self.bytesEdit.textChanged.connect(self.bytes_to_asm)
        super().focusOutEvent(event)

    # 机器码转汇编
    def bytes_to_asm(self):
        try:
            self.text = parse_bytes(self.bytesEdit.toPlainText(), 1)
            self.bytes_code = bytes.fromhex(self.text)
            # 使用ks_asm函数将字节转换为汇编代码
            asm = ''
            for item in self.cs.disasm(self.bytes_code, 0):
                # addr = int(bytes_code) + item.address
                asm += item.mnemonic + " " + item.op_str+'\n'

            self.asmEdit.setText(asm)
        except Exception as e:
            # print(str(e))
            pass

    # 汇编转机器码
    def asm_to_bytes(self):
        try:
            self.asm = self.asmEdit.toPlainText().encode()
            # 使用ks_asm函数将字节转换为汇编代码
            text = ""
            for line in list(filter(None, self.asm.split(b'\n'))):
                encoding, count = self.ks.asm(line, 0)
                text += " ".join([format(c, "02x") for c in encoding])

            self.text = text
            self.bytesEdit.setText(text)
        except Exception as e:
            # print(str(e))
            pass

    def patch_bytes(self):
        start_addr = int(self.startLine.text(), 16)
        end_addr = int(self.endLine.text(), 16)

        if self.option_types[0].isChecked():
            searched_addr = ida_search.find_binary(start_addr,
                                                   end_addr, self.text, 16, ida_search.SEARCH_DOWN)
            while (searched_addr != idc.BADADDR):
                # try:
                #     self.func_list.append(idaapi.get_func(searched_addr).start_ea)
                # except:
                #     print("[!] AUTO RE-DEFINE: NOT FUNCTION AT "+hex(searched_addr))
                textlen = len(self.text.split())
                print("[+] PATCHED "+hex(searched_addr) +
                      ' '+str(textlen)+' BYTES')
                nop(searched_addr, searched_addr+textlen, self.nop_inst)
                searched_addr = ida_search.find_binary(searched_addr+textlen,
                                                       end_addr, self.text, 16, ida_search.SEARCH_DOWN)
        elif self.option_types[1].isChecked():

            bytes_like_pattern = parse_bytes(
                self.bytesEdit.toPlainText(), 0).encode('latin1')
            print(self.bytesEdit.toPlainText())
            addr_range = end_addr-start_addr
            current_bytes = idc.get_bytes(start_addr, addr_range)  # 可能导致卡死
            print(f"[!] bytes:{bytes_like_pattern}")
            res = re.search(bytes_like_pattern, current_bytes, flags=0)

            while (res != None):
                searched_addr = start_addr+res.span()[0]  # 起始地址加上匹配到下标的偏移
                # try:
                #     self.func_list.append(idaapi.get_func(searched_addr).start_ea)
                # except:
                #     print("[!] AUTO RE-DEFINE: NOT FUNCTION AT "+hex(searched_addr))
                textlen = res.span()[1]-res.span()[0]
                nop(searched_addr, searched_addr+textlen, self.nop_inst)
                print("[+] PATCHED "+hex(searched_addr) +
                      ' '+str(textlen)+' BYTES')
                start_addr = searched_addr+textlen
                addr_range = end_addr-start_addr
                current_bytes = idc.get_bytes(start_addr, addr_range)
                res = re.search(bytes_like_pattern, current_bytes, flags=0)

                # searched_addr=''
        print("[+] PATCH END")

    # def re_define(self):
    #     for func in self.func_list:
    #         print(hex(func))
    #         print(idaapi.del_func(func))
    #     for func in self.func_list:
    #         print(hex(func))
    #         print(idaapi.add_func(func))
    #         print(idaapi.mark_cfunc_dirty(func))

    def apply_clicked(self):
        '''
        TODO:
            # 部分匹配 (已完成)
            patch为指定指令 (非nop)
            # 自动U+P重新识别函数 (API有限,无法实现)
            # 选中花指令 右键打开插件界面 (已完成)
            unicorn修复跳转 (开发中)
        '''
        self.patch_bytes()
        # self.re_define()


class test_handler_t(idaapi.action_handler_t):
    def __init__(self, action):
        idaapi.action_handler_t.__init__(self)
        self.action = action

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

    def activate(self, ctx):
        if self.action in "dejunk:open_window":
            f = DejunkForm()
            f.Show(PLUG_NAME)
        if self.action in "dejunk:send_code":
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1):
                start, end = t0.place(view).toea(), t1.place(view).toea()
                size = end - start
            data = idc.get_bytes(start, size)
            name = idc.get_name(start, idc.GN_VISIBLE)
            if not name:
                name = "data"
            if data:
                formated_data = " ".join("%02X" % b for b in data)
            f = DejunkForm(formated_data)
            f.Show(PLUG_NAME)


class UI_Hook(idaapi.UI_Hooks):
    def finish_populating_widget_popup(self, widget, popup):
        if idaapi.get_widget_type(widget) == idaapi.BWN_PSEUDOCODE or idaapi.get_widget_type(widget) == idaapi.BWN_DISASM:
            idaapi.attach_action_to_popup(
                widget, popup, "dejunk:open_window", "Dejunk/")
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1) or idc.get_item_size(idc.get_screen_ea()) > 1:
                idaapi.attach_action_to_popup(
                    widget, popup, "dejunk:send_code", "Dejunk/")


class DejunkPlug(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    wanted_name = "Dejunk"
    comment = "Dejunk"
    help = "An ida plug-in that matches the bulk removal of junk instructions from the program"

    def init(self):
        if not ida_hexrays.init_hexrays_plugin():
            return idaapi.PLUGIN_SKIP
        print("Dejunk initialized!")
        self.registered_actions = []
        menu_actions = (idaapi.action_desc_t("dejunk:send_code", "Send Code to Dejunk", test_handler_t("dejunk:send_code"), None, None,
                                             9),
                        idaapi.action_desc_t("dejunk:open_window", "Open Dejunk Window", test_handler_t("dejunk:open_window"), None, None,
                                             9),)

        for action in menu_actions:
            idaapi.register_action(action)
            self.registered_actions.append(action.name)

        # Add ui hook
        self.ui_hook = UI_Hook()
        res = self.ui_hook.hook()
        print(res)

        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        f = DejunkForm()
        f.Show(PLUG_NAME)
        # MyWidget()

    def term(self):
        if hasattr(self, "ui_hook"):
            self.ui_hook.unhook()
        # Unregister actions
        for action in self.registered_actions:
            idaapi.unregister_action(action)
        pass
        # print("Dejunk terminated!")


def PLUGIN_ENTRY():
    return DejunkPlug()


repository: nihilus/idascope
file: IDAscope.py
url: https://github.com/nihilus/idascope/blob/79d78c0cfde5ba88abde62db686114dd1c0d25d2/IDAscope.py
wanted_name: IDAscope
created_at: 2014-08-31 12:02:19+00:00
pushed_at: 2014-08-31 12:02:52+00:00
content:
#!/usr/bin/python
########################################################################
# Copyright (c) 2012
# Daniel Plohmann <daniel.plohmann<at>gmail<dot>com>
# Alexander Hanel <alexander.hanel<at>gmail<dot>com>
# All rights reserved.
########################################################################
#
#  This file is part of IDAscope
#
#  IDAscope 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 os
import sys
import time

import idc
import idaapi
from idaapi import PluginForm, plugin_t
from PySide import QtGui
from PySide.QtGui import QIcon

import idascope.config as config
from idascope.core.structures.IDAscopeConfiguration import IDAscopeConfiguration
from idascope.core.SemanticIdentifier import SemanticIdentifier
from idascope.core.DocumentationHelper import DocumentationHelper
from idascope.core.WinApiProvider import WinApiProvider
from idascope.core.CryptoIdentifier import CryptoIdentifier
from idascope.core.YaraScanner import YaraScanner
from idascope.core.IdaProxy import IdaProxy
from idascope.widgets.FunctionInspectionWidget import FunctionInspectionWidget
from idascope.widgets.WinApiWidget import WinApiWidget
from idascope.widgets.CryptoIdentificationWidget import CryptoIdentificationWidget
from idascope.widgets.YaraScannerWidget import YaraScannerWidget

################################################################################
# Core of the IDAscope GUI.
################################################################################

HOTKEYS = None
IDASCOPE = None
NAME = "simpliFiRE.IDAscope v1.1"


class IDAscopeForm(PluginForm):
    """
    This class contains the main window of IDAscope
    Setup of core modules and widgets is performed in here.
    """

    def __init__(self):
        super(IDAscopeForm, self).__init__()
        global HOTKEYS
        HOTKEYS = []
        self.idascope_widgets = []
        self.ensureRootPathSanity(config.configuration)
        self.config = IDAscopeConfiguration(config.configuration)
        self.icon = QIcon(self.config.icon_file_path + "idascope.png")

    def ensureRootPathSanity(self, configuration):
        try:
            root_dir = configuration["paths"]["idascope_root_dir"]
            if not os.path.exists(root_dir) or not "IDAscope.py" in os.listdir(root_dir):
                print "[!] IDAscope.py is not present in root directory specified in \"config.py\", " \
                     + "trying to resolve path..."
                resolved_pathname = os.path.dirname(sys.argv[0])
                if "IDAscope.py" in os.listdir(resolved_pathname):
                    print "[+] IDAscope root directory successfully resolved."
                    configuration["paths"]["idascope_root_dir"] = resolved_pathname
                else:
                    print "[-] IDAscope.py is not resolvable!"
                    raise Exception()
        except:
            print "[!] IDAscope config is broken. Could not locate root directory. " \
                 + "Try setting the field \"idascope_root_dir\" to the path where \"IDAscope.py\" is located."
            sys.exit(-1)

    def setupSharedModules(self):
        """
        Setup shared IDAscope modules.
        """
        time_before = time.time()
        print ("[/] setting up shared modules...")
        self.documentation_helper = DocumentationHelper(self.config)
        self.semantic_identifier = SemanticIdentifier(self.config)
        self.winapi_provider = WinApiProvider(self.config)
        self.crypto_identifier = CryptoIdentifier()
        self.yara_scanner = YaraScanner(self.config)
        self.ida_proxy = IdaProxy()
        print ("[\\] this took %3.2f seconds.\n" % (time.time() - time_before))

    def setupWidgets(self):
        """
        Setup IDAscope widgets.
        """
        time_before = time.time()
        print ("[/] setting up widgets...")
        self.idascope_widgets.append(FunctionInspectionWidget(self))
        self.idascope_widgets.append(WinApiWidget(self))
        self.idascope_widgets.append(CryptoIdentificationWidget(self))
        self.idascope_widgets.append(YaraScannerWidget(self))
        self.setupIDAscopeForm()
        print ("[\\] this took %3.2f seconds.\n" % (time.time() - time_before))

    def setupIDAscopeForm(self):
        """
        Orchestrate the already initialized widgets in tabs on the main window.
        """
        self.tabs = QtGui.QTabWidget()
        self.tabs.setTabsClosable(False)
        for widget in self.idascope_widgets:
            self.tabs.addTab(widget, widget.icon, widget.name)
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.tabs)
        self.parent.setLayout(layout)

    def OnCreate(self, form):
        """
        When creating the form, setup the shared modules and widgets
        """
        self.printBanner()
        self.parent = self.FormToPySideWidget(form)
        self.parent.setWindowIcon(self.icon)
        self.setupSharedModules()
        self.setupWidgets()

    def printBanner(self):
        banner = "#############################################\n" \
               + "  ___ ____    _                             \n" \
               + " |_ _|  _ \  / \   ___  ___ ___  _ __   ___ \n" \
               + "  | || | | |/ _ \ / __|/ __/ _ \\| '_ \\ / _ \\\n" \
               + "  | || |_| / ___ \\\\__ \\ (_| (_) | |_) |  __/\n" \
               + " |___|____/_/   \\_\\___/\\___\\___/| .__/ \\___|\n" \
               + "                                |_|         \n" \
               + "#############################################\n" \
               + " by Daniel Plohmann and Alexander Hanel      \n" \
               + "#############################################\n"
        print banner
        print ("[+] Loading simpliFiRE.IDAscope")

    def OnClose(self, form):
        """
        Perform cleanup.
        """
        global IDASCOPE
        del IDASCOPE

    def Show(self):
        if idc.GetInputMD5() == None:
            return
        else:
            return PluginForm.Show(self,
                NAME,
                options=(PluginForm.FORM_CLOSE_LATER | PluginForm.FORM_RESTORE | PluginForm.FORM_SAVE))

################################################################################
# functionality offered to IDAscope's widgets
################################################################################

    def setTabFocus(self, widget_name):
        """
        Can be used by IDAscope widgets to set focus to a widget, identified by name.
        @param widget_name: A widget name
        @type widget_name: str
        """
        for widget in self.idascope_widgets:
            if widget.name == widget_name:
                tab_index = self.tabs.indexOf(widget)
                self.tabs.setCurrentIndex(tab_index)
        return

    def registerHotkey(self, shortcut, py_function_pointer):
        """
        Can be used by IDAscope widgets to register hotkeys.
        Uses a global list HOTKEYS of function pointers that link to the desired functionality.
        Right now, linked functions cannot take parameters and should scrape all information they need by themselves.
        @param shortcut: A string describing a shortcut, e.g. "ctrl+F3"
        @type shortcut: str
        @param py_function_pointer: a python function that shall be called when the shortcut is triggered.
        @type py_function_pointer: a pointer to a python function
        """
        global HOTKEYS
        hotkey_index = len(HOTKEYS)
        hotkey_name = "idascope_HOTKEY_%d" % hotkey_index
        HOTKEYS.append(py_function_pointer)
        self.ida_proxy.CompileLine('static %s() { RunPythonStatement("HOTKEYS[%d]()"); }' % (hotkey_name, hotkey_index))
        self.ida_proxy.AddHotkey(shortcut, hotkey_name)

################################################################################
# Usage as plugin
################################################################################


def PLUGIN_ENTRY():
    return IDAscopePlugin()


class IDAscopePlugin(plugin_t):
    """
    Plugin version of IDAscope. Use this to deploy IDAscope via IDA plugins folder.
    """
    flags = idaapi.PLUGIN_UNL
    comment = NAME
    help = "IDAscope - Different tools to ease reverse engineering."
    wanted_name = "IDAscope"
    wanted_hotkey = "Ctrl-F4"

    def init(self):
        # Some initialization
        self.icon_id = 0
        return idaapi.PLUGIN_OK

    def run(self, arg=0):
        # Create form
        f = IDAscopeForm()
        # Show the form
        f.Show()
        return

    def term(self):
        pass

################################################################################
# Usage as script
################################################################################


def main():
    global IDASCOPE
    try:
        IDASCOPE
        IDASCOPE.OnClose(IDASCOPE)
        print ("reloading IDAscope")
        IDASCOPE = IDAscopeForm()
        return
    except Exception:
        IDASCOPE = IDAscopeForm()

    if IDASCOPE.config.idascope_plugin_only:
        print "IDAscope: configured as plugin-only mode, ignoring main function of script. " \
             + "This can be changed in \"idascope/config.py\"."
    else:
        IDASCOPE.Show()


if __name__ == "__main__":
    main()


repository: tmr232/Sark
file: plugins/plugin_loader.py
description: IDAPython Made Easy
wanted_name: PluginLoader
comment: Plugin Loader
created_at: 2015-01-19 09:13:48+00:00
pushed_at: 2025-02-22 10:17:16+00:00
url:
https://github.com/tmr232/Sark/blob/950e2c17e520ab637a3cdcd0da312d224b951f13/plugins/plugin_loader.py

content:
import os
import itertools
import idaapi
import idc

PLUGINS_LIST = "plugins.list"

USER_PLUGIN_LIST_PATH = os.path.join(idaapi.get_user_idadir(), PLUGINS_LIST)
SYS_PLUGIN_LIST_PATH = os.path.join(idaapi.idadir(idaapi.CFG_SUBDIR), PLUGINS_LIST)
if idc.get_idb_path():
    PROJECT_PLUGIN_LIST_PATH = os.path.join(os.path.dirname(idc.get_idb_path()), PLUGINS_LIST)
else:
    PROJECT_PLUGIN_LIST_PATH = None


def message(*messages):
    for msg in messages:
        for line in msg.splitlines():
            idaapi.msg("[PluginLoader] {}\n".format(line))


def iter_without_duplicates(*iterables):
    visited = set()
    chained_iterables = itertools.chain(*iterables)
    for item in chained_iterables:
        if item in visited:
            continue
        yield item
        visited.add(item)


def iter_paths(filepath):
    if not filepath:
        return
    try:
        with open(filepath) as f:
            for line in f:
                # Use `#` for comments
                if line.startswith("#"):
                    continue
                # Remove trailing spaces and newlines, then normalize to avoid duplicates.
                path = os.path.normpath(line.strip())
                if path:
                    yield path
    except IOError:
        pass


def iter_plugin_paths():
    return iter_without_duplicates(iter_paths(SYS_PLUGIN_LIST_PATH),
                                   iter_paths(USER_PLUGIN_LIST_PATH),
                                   iter_paths(PROJECT_PLUGIN_LIST_PATH))


class PluginLoader(idaapi.plugin_t):
    flags = idaapi.PLUGIN_FIX
    comment = "Plugin Loader"
    help = "Plugin Loader"
    wanted_name = "PluginLoader"
    wanted_hotkey = ""

    def init(self):
        # Show usage message.
        usage_message = ["Loading plugins from system-wide and user-specific lists:",
                         "  System-wide List:      {}".format(SYS_PLUGIN_LIST_PATH),
                         "  User-specific List:    {}".format(USER_PLUGIN_LIST_PATH)]
        if PROJECT_PLUGIN_LIST_PATH:
            usage_message.append("  Project-specific List: {}".format(PROJECT_PLUGIN_LIST_PATH))

        message(*usage_message)

        # Make sure the files exist. If not - create them.
        if not os.path.isfile(SYS_PLUGIN_LIST_PATH):
            try:
                with open(SYS_PLUGIN_LIST_PATH, "wb"):
                    message("Created system plugin list at {}".format(SYS_PLUGIN_LIST_PATH))
            except IOError:
                message("Failed creating system plugin list at {}".format(SYS_PLUGIN_LIST_PATH))

        if not os.path.isfile(USER_PLUGIN_LIST_PATH):
            try:
                with open(USER_PLUGIN_LIST_PATH, "wb"):
                    message("Created user plugin list at {}".format(USER_PLUGIN_LIST_PATH))
            except IOError:
                message("Failed creating user plugin list at {}".format(USER_PLUGIN_LIST_PATH))

        for path in iter_plugin_paths():
            # This check is not needed, but saves us from the dreaded error message-box
            # that pops when a python plugin is not found.
            if not os.path.isfile(path):
                message("Plugin file not found: {}".format(path))
                continue
            idaapi.load_plugin(path)
        return idaapi.PLUGIN_SKIP

    def term(self):
        pass

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return PluginLoader()


repository: Accenture/FirmLoader
file: firmloader.py
url: https://github.com/Accenture/FirmLoader/blob/5c9c1a149b2b638d1e2b65d39723b6a45379fdb1/firmloader.py
wanted_name: FirmLoader
comment: FirmLoader
created_at: 2022-06-09 06:21:11+00:00
pushed_at: 2024-12-05 11:36:37+00:00
content:
import os
import json
import idaapi
import idc
import ida_funcs
import ida_segment
import ida_segment
import ida_xref
import ida_bytes
import idautils
import ida_kernwin
import ida_ida


class FirmwareChooser(ida_kernwin.Choose):
    def __init__(self, firmwares):
        ida_kernwin.Choose.__init__(self, "Select firmware", [["Name", 30 | ida_kernwin.CHCOL_PLAIN]], flags = ida_kernwin.CH_QFLT)
        self.firmware_list = firmwares

    def OnGetSize(self):
        return len(self.firmware_list)

    def OnGetLine(self, n):
        return [self.firmware_list[n]["name"]]


class Firmloader(idaapi.action_handler_t):
    def __init__(self):
        idaapi.action_handler_t.__init__(self)

    def activate(self, ctx):
        firmware_list = []
        for file_name in sorted(os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),"firmloader_data"))):
            with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),"firmloader_data",file_name)) as data_file:
                data = json.load(data_file)
                key = file_name.replace(".json","")
                if data["family"]:
                    display_name = data["brand"] + "/" + data["family"] + "/"+ data["name"]
                else:
                    display_name = data["brand"] + "/" + data["name"]

                firmware_list.append({"key": key, "name": display_name})

        chooser = FirmwareChooser(firmware_list)
        if (sel := chooser.Show(True)) == -1:
            return

        chosen_key = firmware_list[sel]["key"]

        found_rom = False
        rom_segment = 0
        for segment_ea in idautils.Segments():
                # Default ROM segment
                if idc.get_segm_name(segment_ea) == "ROM":
                    found_rom = True
                    rom_segment = segment_ea
        # If there is no ROM segment warn user and close
        if not found_rom:
            ida_kernwin.warning("Segment with name \"ROM\" does not exist. Please, rename the main code segment to \"ROM\" and run the plugin again.")
            return
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "firmloader_data", chosen_key + ".json")) as data_file:
            current_mcu = json.load(data_file)
            # Create segments
            for segment in current_mcu["segments"]:
                start = int(segment["start"],16)
                end = int(segment["end"],16)
                ida_segment.add_segm(0,start,end,segment["name"],segment["type"],0)
                # Set mode
            for segment_ea in idautils.Segments():
                # Set mode Thumb/ARM if ARM architecture
                _procname = ida_ida.inf_get_procname().lower() if idaapi.IDA_SDK_VERSION >= 900 else idaapi.get_inf_structure()
                if _procname == 'ARM' and found_rom:
                    idaapi.split_sreg_range(rom_segment, idaapi.str2reg("T"), current_mcu["mode"], idaapi.SR_user)
            # Create peripherals
            if ida_kernwin.ask_yn(1, "Would you like to load peripherals?"):
                for peripheral in current_mcu["peripherals"]:
                    # Start of the peripheral struct
                    start = int(peripheral["start"],16)
                    end = int(peripheral["end"],16)
                    ida_segment.add_segm(0,start,end,peripheral["name"],"DATA",0)
                    if peripheral["registers"]:
                        for register in peripheral["registers"]:
                            offset = int(register["offset"],16)
                            idc.set_name(start + offset, f'{peripheral["name"]}_{register["name"]}', idc.SN_NOCHECK)
                    # Add comment if any
                    idc.set_cmt(start,peripheral["comment"],False)
            # Crate vector table
            if current_mcu["vector_table"]:
                if ida_kernwin.ask_yn(1, "Would you like to also populate the vector table?"):
                    vector_address = ida_kernwin.ask_str(hex(rom_segment),0,"Set vector table at address:")
                    try:
                        if "x" in vector_address:
                            vector_address = int(vector_address,16)
                        else:
                            vector_address = int(vector_address)
                    except:
                        ida_kernwin.info("Invalid address!")
                        return
                    if not idc.get_segm_name(vector_address):
                        ida_kernwin.info("Invalid address!")
                        return
                    else:
                        for vector in current_mcu["vector_table"]:
                            # 
                            addr = vector_address + int(vector["addr"],16)
                            # Create a data value
                            if current_mcu["bits"] == 8:
                                ida_bytes.create_byte(addr,1)
                            elif current_mcu["bits"] == 16:
                                ida_bytes.create_qword(addr,2)
                            elif current_mcu["bits"] == 32:
                                ida_bytes.create_dword(addr,4)
                            elif current_mcu["bits"] == 64:
                                ida_bytes.create_qword(addr,8)

                            # Set name of the address
                            idc.set_name(addr, vector["name"]+"_vector", idc.SN_NOCHECK)
                            # Set comment
                            idc.set_cmt(addr,vector["comment"],False)
                            # Get XREF destination
                            destination = ida_bytes.get_32bit(addr)
                            # If ARM account for Thumb mode
                            _procname = ida_ida.inf_get_procname().lower() if idaapi.IDA_SDK_VERSION >= 900 else idaapi.get_inf_structure()
                            if _procname == 'ARM' and destination % 2 == 1: # Thumb mode
                                destination = destination - 1
                            # If the segment is CODE
                            if ida_segment.get_segm_class(ida_segment.getseg(destination)) == "CODE":
                                # Create code reference
                                ida_xref.add_cref(addr,destination,1)
                                # If the name does not exist or is unk_*
                                if idc.get_name(destination) == "" or idc.get_name(destination).startswith("unk_"):
                                    ida_funcs.add_func(destination)
                                    idc.set_name(destination, vector["name"] + "_handler", idc.SN_NOCHECK | idc.SN_PUBLIC)
            # All is done at this point, mark the ROM segment for auto-analysis
            if found_rom:
                idc.auto_mark_range(rom_segment,idc.get_segm_end(rom_segment),idaapi.AU_WEAK) # Let IDA decide what is code and what is not

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

class firmloader_t(idaapi.plugin_t):
    comment = "FirmLoader"
    help = "This script helps when working with various microcontroller firmware files."
    wanted_name = "FirmLoader"
    wanted_hotkey = ""
    flags = idaapi.PLUGIN_KEEP

    def init(self):
        load_action_id = "firmloader:load"
        load_action = idaapi.action_desc_t(
            load_action_id,
            "Apply firmware info...",
            Firmloader(),
            '',
            "Apply known firmware info to the database."
        )

        idaapi.register_action(load_action)
        idaapi.attach_action_to_menu("Edit/FirmLoader/Apply firmware info...", load_action_id, idaapi.SETMENU_APP)

    def run(self):
        pass

    def term(self):
        pass

def PLUGIN_ENTRY():
    return firmloader_t()


repository: NL2Code/CodeS
file: repos/LazyIDA-master/LazyIDA.py
wanted_name: LazyIDA
comment: LazyIDA
created_at: 2024-03-25 04:20:11+00:00
pushed_at: 2024-07-20 08:01:55+00:00
url:
https://github.com/NL2Code/CodeS/blob/0b624ab4ef22b0d9d223f274a986eb27fe090c88/repos/LazyIDA-master/LazyIDA.py

content:
from __future__ import division
from __future__ import print_function
from struct import unpack
import idaapi
import idautils
import idc

from PyQt5.Qt import QApplication

ACTION_CONVERT = ["lazyida:convert%d" % i for i in range(10)]
ACTION_SCANVUL = "lazyida:scanvul"
ACTION_COPYEA = "lazyida:copyea"
ACTION_GOTOCLIP = "lazyida:gotoclip"
ACTION_XORDATA = "lazyida:xordata"
ACTION_FILLNOP = "lazyida:fillnop"

ACTION_HX_REMOVERETTYPE = "lazyida:hx_removerettype"
ACTION_HX_COPYEA = "lazyida:hx_copyea"
ACTION_HX_COPYNAME = "lazyida:hx_copyname"
ACTION_HX_GOTOCLIP = "lazyida:hx_gotoclip"

u16 = lambda x: unpack("<H", x)[0]
u32 = lambda x: unpack("<I", x)[0]
u64 = lambda x: unpack("<Q", x)[0]

ARCH = 0
BITS = 0

def copy_to_clip(data):
    QApplication.clipboard().setText(data)

def clip_text():
    return QApplication.clipboard().text()

def parse_location(loc):
    try:
        loc = int(loc, 16)
    except ValueError:
        try:
            loc = idc.get_name_ea_simple(loc.encode().strip())
        except:
            return idaapi.BADADDR
    return loc

class VulnChoose(idaapi.Choose):
    """
    Chooser class to display result of format string vuln scan
    """
    def __init__(self, title, items, icon, embedded=False):
        idaapi.Choose.__init__(self, title, [["Address", 20], ["Function", 30], ["Format", 30]], embedded=embedded)
        self.items = items
        self.icon = 45

    def GetItems(self):
        return self.items

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

    def OnClose(self):
        pass

    def OnGetLine(self, n):
        return self.items[n]

    def OnGetSize(self):
        return len(self.items)

    def OnSelectLine(self, n):
        idc.jumpto(int(self.items[n][0], 16))

class hotkey_action_handler_t(idaapi.action_handler_t):
    """
    Action handler for hotkey actions
    """
    def __init__(self, action):
        idaapi.action_handler_t.__init__(self)
        self.action = action

    def activate(self, ctx):
        if self.action == ACTION_COPYEA:
            ea = idc.get_screen_ea()
            if ea != idaapi.BADADDR:
                copy_to_clip("0x%X" % ea)
                print("Address 0x%X has been copied to clipboard" % ea)
        elif self.action == ACTION_GOTOCLIP:
            loc = parse_location(clip_text())
            if loc != idaapi.BADADDR:
                print("Goto location 0x%x" % loc)
                idc.jumpto(loc)
        return 1

    def update(self, ctx):
        if idaapi.IDA_SDK_VERSION >= 770:
            target_attr = "widget_type"
        else:
            target_attr = "form_type"
        if ctx.__getattribute__(target_attr) in (idaapi.BWN_DISASM, idaapi.BWN_DUMP):
            return idaapi.AST_ENABLE_FOR_WIDGET
        else:
            return idaapi.AST_DISABLE_FOR_WIDGET

class menu_action_handler_t(idaapi.action_handler_t):
    """
    Action handler for menu actions
    """
    def __init__(self, action):
        idaapi.action_handler_t.__init__(self)
        self.action = action

    def activate(self, ctx):
        if self.action in ACTION_CONVERT:
            # convert
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1):
                start, end = t0.place(view).toea(), t1.place(view).toea()
                size = end - start + 1
            elif idc.get_item_size(idc.get_screen_ea()) > 1:
                start = idc.get_screen_ea()
                size = idc.get_item_size(start)
                end = start + size
            else:
                return False

            data = idc.get_bytes(start, size)
            if isinstance(data, str):  # python2 compatibility
                data = bytearray(data)
            name = idc.get_name(start, idc.GN_VISIBLE)
            if not name:
                name = "data"
            if data:
                print("\n[+] Dump 0x%X - 0x%X (%u bytes) :" % (start, end, size))
                if self.action == ACTION_CONVERT[0]:
                    # escaped string
                    output = '"%s"' % "".join("\\x%02X" % b for b in data)
                elif self.action == ACTION_CONVERT[1]:
                    # hex string
                    output = "".join("%02X" % b for b in data)
                elif self.action == ACTION_CONVERT[2]:
                    # C array
                    output = "unsigned char %s[%d] = {" % (name, size)
                    for i in range(size):
                        if i % 16 == 0:
                            output += "\n    "
                        output += "0x%02X, " % data[i]
                    output = output[:-2] + "\n};"
                elif self.action == ACTION_CONVERT[3]:
                    # C array word
                    data += b"\x00"
                    array_size = (size + 1) // 2
                    output = "unsigned short %s[%d] = {" % (name, array_size)
                    for i in range(0, size, 2):
                        if i % 16 == 0:
                            output += "\n    "
                        output += "0x%04X, " % u16(data[i:i+2])
                    output = output[:-2] + "\n};"
                elif self.action == ACTION_CONVERT[4]:
                    # C array dword
                    data += b"\x00" * 3
                    array_size = (size + 3) // 4
                    output = "unsigned int %s[%d] = {" % (name, array_size)
                    for i in range(0, size, 4):
                        if i % 32 == 0:
                            output += "\n    "
                        output += "0x%08X, " % u32(data[i:i+4])
                    output = output[:-2] + "\n};"
                elif self.action == ACTION_CONVERT[5]:
                    # C array qword
                    data += b"\x00" * 7
                    array_size = (size + 7) // 8
                    output = "unsigned long %s[%d] = {" % (name, array_size)
                    for i in range(0, size, 8):
                        if i % 32 == 0:
                            output += "\n    "
                        output += "0x%016X, " % u64(data[i:i+8])
                    output = output[:-2] + "\n};"
                elif self.action == ACTION_CONVERT[6]:
                    # python list
                    output = "[%s]" % ", ".join("0x%02X" % b for b in data)
                elif self.action == ACTION_CONVERT[7]:
                    # python list word
                    data += b"\x00"
                    output = "[%s]" % ", ".join("0x%04X" % u16(data[i:i+2]) for i in range(0, size, 2))
                elif self.action == ACTION_CONVERT[8]:
                    # python list dword
                    data += b"\x00" * 3
                    output = "[%s]" % ", ".join("0x%08X" % u32(data[i:i+4]) for i in range(0, size, 4))
                elif self.action == ACTION_CONVERT[9]:
                    # python list qword
                    data += b"\x00" * 7
                    output = "[%s]" %  ", ".join("%#018X" % u64(data[i:i+8]) for i in range(0, size, 8)).replace("0X", "0x")
                copy_to_clip(output)
                print(output)
        elif self.action == ACTION_XORDATA:
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1):
                start, end = t0.place(view).toea(), t1.place(view).toea()
            else:
                if idc.get_item_size(idc.get_screen_ea()) > 1:
                    start = idc.get_screen_ea()
                    end = start + idc.get_item_size(start)
                else:
                    return False

            data = idc.get_bytes(start, end - start)
            if isinstance(data, str):  # python2 compatibility
                data = bytearray(data)
            x = idaapi.ask_long(0, "Xor with...")
            if x:
                x &= 0xFF
                print("\n[+] Xor 0x%X - 0x%X (%u bytes) with 0x%02X:" % (start, end, end - start, x))
                print(repr("".join(chr(b ^ x) for b in data)))
        elif self.action == ACTION_FILLNOP:
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1):
                start, end = t0.place(view).toea(), t1.place(view).toea()
                idaapi.patch_bytes(start, b"\x90" * (end - start))
                print("\n[+] Fill 0x%X - 0x%X (%u bytes) with NOPs" % (start, end, end - start))
        elif self.action == ACTION_SCANVUL:
            print("\n[+] Finding Format String Vulnerability...")
            found = []
            for addr in idautils.Functions():
                name = idc.get_func_name(addr)
                if "printf" in name and "v" not in name and idc.get_segm_name(addr) in (".text", ".plt", ".idata", ".plt.got"):
                    xrefs = idautils.CodeRefsTo(addr, False)
                    for xref in xrefs:
                        vul = self.check_fmt_function(name, xref)
                        if vul:
                            found.append(vul)
            if found:
                print("[!] Done! %d possible vulnerabilities found." % len(found))
                ch = VulnChoose("Vulnerability", found, None, False)
                ch.Show()
            else:
                print("[-] No format string vulnerabilities found.")
        else:
            return 0

        return 1

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

    @staticmethod
    def check_fmt_function(name, addr):
        """
        Check if the format string argument is not valid
        """
        function_head = idc.get_func_attr(addr, idc.FUNCATTR_START)

        while True:
            addr = idc.prev_head(addr)
            op = idc.print_insn_mnem(addr).lower()
            dst = idc.print_operand(addr, 0)

            if op in ("ret", "retn", "jmp", "b") or addr < function_head:
                return

            c = idc.get_cmt(addr, 0)
            if c and c.lower() == "format":
                break
            elif name.endswith(("snprintf_chk",)):
                if op in ("mov", "lea") and dst.endswith(("r8", "r8d", "[esp+10h]")):
                    break
            elif name.endswith(("sprintf_chk",)):
                if op in ("mov", "lea") and (dst.endswith(("rcx", "[esp+0Ch]", "R3")) or
                                             dst.endswith("ecx") and BITS == 64):
                    break
            elif name.endswith(("snprintf", "fnprintf")):
                if op in ("mov", "lea") and (dst.endswith(("rdx", "[esp+8]", "R2")) or
                                             dst.endswith("edx") and BITS== 64):
                    break
            elif name.endswith(("sprintf", "fprintf", "dprintf", "printf_chk")):
                if op in ("mov", "lea") and (dst.endswith(("rsi", "[esp+4]", "R1")) or
                                             dst.endswith("esi") and BITS == 64):
                    break
            elif name.endswith("printf"):
                if op in ("mov", "lea") and (dst.endswith(("rdi", "[esp]", "R0")) or
                                             dst.endswith("edi") and BITS == 64):
                    break

        # format arg found, check its type and value
        # get last oprend
        op_index = idc.generate_disasm_line(addr, 0).count(",")
        op_type = idc.get_operand_type(addr, op_index)
        opnd = idc.print_operand(addr, op_index)

        if op_type == idc.o_reg:
            # format is in register, try to track back and get the source
            _addr = addr
            while True:
                _addr = idc.prev_head(_addr)
                _op = idc.print_insn_mnem(_addr).lower()
                if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:
                    break
                elif _op in ("mov", "lea", "ldr") and idc.print_operand(_addr, 0) == opnd:
                    op_type = idc.get_operand_type(_addr, 1)
                    opnd = idc.print_operand(_addr, 1)
                    addr = _addr
                    break

        if op_type == idc.o_imm or op_type == idc.o_mem:
            # format is a memory address, check if it's in writable segment
            op_addr = idc.get_operand_value(addr, op_index)
            seg = idaapi.getseg(op_addr)
            if seg:
                if not seg.perm & idaapi.SEGPERM_WRITE:
                    # format is in read-only segment
                    return

        print("0x%X: Possible Vulnerability: %s, format = %s" % (addr, name, opnd))
        return ["0x%X" % addr, name, opnd]

class hexrays_action_handler_t(idaapi.action_handler_t):
    """
    Action handler for hexrays actions
    """
    def __init__(self, action):
        idaapi.action_handler_t.__init__(self)
        self.action = action
        self.ret_type = {}

    def activate(self, ctx):
        if self.action == ACTION_HX_REMOVERETTYPE:
            vdui = idaapi.get_widget_vdui(ctx.widget)
            self.remove_rettype(vdui)
            vdui.refresh_ctext()
        elif self.action == ACTION_HX_COPYEA:
            ea = idaapi.get_screen_ea()
            if ea != idaapi.BADADDR:
                copy_to_clip("0x%X" % ea)
                print("Address 0x%X has been copied to clipboard" % ea)
        elif self.action == ACTION_HX_COPYNAME:
            highlight = idaapi.get_highlight(idaapi.get_current_viewer()) 
            name = highlight[0] if highlight else None
            if name:
                copy_to_clip(name)
                print("%s has been copied to clipboard" % name)
        elif self.action == ACTION_HX_GOTOCLIP:
            loc = parse_location(clip_text())
            print("Goto location 0x%x" % loc)
            idc.jumpto(loc)
        else:
            return 0

        return 1

    def update(self, ctx):
        vdui = idaapi.get_widget_vdui(ctx.widget)
        return idaapi.AST_ENABLE_FOR_WIDGET if vdui else idaapi.AST_DISABLE_FOR_WIDGET

    def remove_rettype(self, vu):
        if vu.item.citype == idaapi.VDI_FUNC:
            # current function
            ea = vu.cfunc.entry_ea
            old_func_type = idaapi.tinfo_t()
            if not vu.cfunc.get_func_type(old_func_type):
                return False
        elif vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr() and vu.item.e.type.is_funcptr():
            # call xxx
            ea = vu.item.get_ea()
            old_func_type = idaapi.tinfo_t()

            func = idaapi.get_func(ea)
            if func:
                try:
                    cfunc = idaapi.decompile(func)
                except idaapi.DecompilationFailure:
                    return False

                if not cfunc.get_func_type(old_func_type):
                    return False
            else:
                return False
        else:
            return False

        fi = idaapi.func_type_data_t()
        if ea != idaapi.BADADDR and old_func_type.get_func_details(fi):
            # Return type is already void
            if fi.rettype.is_decl_void():
                # Restore ret type
                if ea not in self.ret_type:
                    return True
                ret = self.ret_type[ea]
            else:
                # Save ret type and change it to void
                self.ret_type[ea] = fi.rettype
                ret = idaapi.BT_VOID

            # Create new function info with new rettype
            fi.rettype = idaapi.tinfo_t(ret)

            # Create new function type with function info
            new_func_type = idaapi.tinfo_t()
            new_func_type.create_func(fi)

            # Apply new function type
            if idaapi.apply_tinfo(ea, new_func_type, idaapi.TINFO_DEFINITE):
                return vu.refresh_view(True)

        return False

class UI_Hook(idaapi.UI_Hooks):
    def __init__(self):
        idaapi.UI_Hooks.__init__(self)

    def finish_populating_widget_popup(self, form, popup):
        form_type = idaapi.get_widget_type(form)

        if form_type == idaapi.BWN_DISASM or form_type == idaapi.BWN_DUMP:
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1) or idc.get_item_size(idc.get_screen_ea()) > 1:
                idaapi.attach_action_to_popup(form, popup, ACTION_XORDATA, None)
                idaapi.attach_action_to_popup(form, popup, ACTION_FILLNOP, None)
                for action in ACTION_CONVERT:
                    idaapi.attach_action_to_popup(form, popup, action, "Convert/")

        if form_type == idaapi.BWN_DISASM and (ARCH, BITS) in [(idaapi.PLFM_386, 32),
                                                               (idaapi.PLFM_386, 64),
                                                               (idaapi.PLFM_ARM, 32),]:
            idaapi.attach_action_to_popup(form, popup, ACTION_SCANVUL, None)


class HexRays_Hook(object):
    def callback(self, event, *args):
        if event == idaapi.hxe_populating_popup:
            form, phandle, vu = args
            if vu.item.citype == idaapi.VDI_FUNC or (vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr() and vu.item.e.type.is_funcptr()):
                idaapi.attach_action_to_popup(form, phandle, ACTION_HX_REMOVERETTYPE, None)
        elif event == idaapi.hxe_double_click:
            vu, shift_state = args
            # auto jump to target if clicked item is xxx->func();
            if vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr():
                expr = idaapi.tag_remove(vu.item.e.print1(None))
                if "->" in expr:
                    # find target function
                    name = expr.split("->")[-1]
                    addr = idc.get_name_ea_simple(name)
                    if addr == idaapi.BADADDR:
                        # try class::function
                        e = vu.item.e
                        while e.x:
                            e = e.x
                        addr = idc.get_name_ea_simple("%s::%s" % (str(e.type).split()[0], name))

                    if addr != idaapi.BADADDR:
                        idc.jumpto(addr)
                        return 1
        return 0

class LazyIDA_t(idaapi.plugin_t):
    flags = idaapi.PLUGIN_HIDE
    comment = "LazyIDA"
    help = ""
    wanted_name = "LazyIDA"
    wanted_hotkey = ""

    def init(self):
        self.hexrays_inited = False
        self.registered_actions = []
        self.registered_hx_actions = []

        global ARCH
        global BITS
        ARCH = idaapi.ph_get_id()
        info = idaapi.get_inf_structure()
        if info.is_64bit():
            BITS = 64
        elif info.is_32bit():
            BITS = 32
        else:
            BITS = 16

        print("LazyIDA (v1.0.0.3) plugin has been loaded.")

        # Register menu actions
        menu_actions = (
            idaapi.action_desc_t(ACTION_CONVERT[0], "Convert to string", menu_action_handler_t(ACTION_CONVERT[0]), None, None, 80),
            idaapi.action_desc_t(ACTION_CONVERT[1], "Convert to hex string", menu_action_handler_t(ACTION_CONVERT[1]), None, None, 8),
            idaapi.action_desc_t(ACTION_CONVERT[2], "Convert to C/C++ array (BYTE)", menu_action_handler_t(ACTION_CONVERT[2]), None, None, 38),
            idaapi.action_desc_t(ACTION_CONVERT[3], "Convert to C/C++ array (WORD)", menu_action_handler_t(ACTION_CONVERT[3]), None, None, 38),
            idaapi.action_desc_t(ACTION_CONVERT[4], "Convert to C/C++ array (DWORD)", menu_action_handler_t(ACTION_CONVERT[4]), None, None, 38),
            idaapi.action_desc_t(ACTION_CONVERT[5], "Convert to C/C++ array (QWORD)", menu_action_handler_t(ACTION_CONVERT[5]), None, None, 38),
            idaapi.action_desc_t(ACTION_CONVERT[6], "Convert to python list (BYTE)", menu_action_handler_t(ACTION_CONVERT[6]), None, None, 201),
            idaapi.action_desc_t(ACTION_CONVERT[7], "Convert to python list (WORD)", menu_action_handler_t(ACTION_CONVERT[7]), None, None, 201),
            idaapi.action_desc_t(ACTION_CONVERT[8], "Convert to python list (DWORD)", menu_action_handler_t(ACTION_CONVERT[8]), None, None, 201),
            idaapi.action_desc_t(ACTION_CONVERT[9], "Convert to python list (QWORD)", menu_action_handler_t(ACTION_CONVERT[9]), None, None, 201),
            idaapi.action_desc_t(ACTION_XORDATA, "Get xored data", menu_action_handler_t(ACTION_XORDATA), None, None, 9),
            idaapi.action_desc_t(ACTION_FILLNOP, "Fill with NOPs", menu_action_handler_t(ACTION_FILLNOP), None, None, 9),
            idaapi.action_desc_t(ACTION_SCANVUL, "Scan format string vulnerabilities", menu_action_handler_t(ACTION_SCANVUL), None, None, 160),
        )
        for action in menu_actions:
            idaapi.register_action(action)
            self.registered_actions.append(action.name)

        # Register hotkey actions
        hotkey_actions = (
            idaapi.action_desc_t(ACTION_COPYEA, "Copy EA", hotkey_action_handler_t(ACTION_COPYEA), "w", "Copy current EA", 0),
            idaapi.action_desc_t(ACTION_GOTOCLIP, "Goto clip EA", hotkey_action_handler_t(ACTION_GOTOCLIP), "Shift-G", "Goto clipboard EA", 0),
        )
        for action in hotkey_actions:
            idaapi.register_action(action)
            self.registered_actions.append(action.name)

        # Add ui hook
        self.ui_hook = UI_Hook()
        self.ui_hook.hook()

        # Add hexrays ui callback
        if idaapi.init_hexrays_plugin():
            addon = idaapi.addon_info_t()
            addon.id = "tw.l4ys.lazyida"
            addon.name = "LazyIDA"
            addon.producer = "Lays"
            addon.url = "https://github.com/L4ys/LazyIDA"
            addon.version = "1.0.0.3"
            idaapi.register_addon(addon)

            hx_actions = (
                idaapi.action_desc_t(ACTION_HX_REMOVERETTYPE, "Remove return type", hexrays_action_handler_t(ACTION_HX_REMOVERETTYPE), "v"),
                idaapi.action_desc_t(ACTION_HX_COPYEA, "Copy ea", hexrays_action_handler_t(ACTION_HX_COPYEA), "w"),
                idaapi.action_desc_t(ACTION_HX_COPYNAME, "Copy name", hexrays_action_handler_t(ACTION_HX_COPYNAME), "c"),
                idaapi.action_desc_t(ACTION_HX_GOTOCLIP, "Goto clipboard ea", hexrays_action_handler_t(ACTION_HX_GOTOCLIP), "Shift-G"),
            )
            for action in hx_actions:
                idaapi.register_action(action)
                self.registered_hx_actions.append(action.name)

            self.hx_hook = HexRays_Hook()
            idaapi.install_hexrays_callback(self.hx_hook.callback)
            self.hexrays_inited = True

        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        pass

    def term(self):
        if hasattr(self, "ui_hook"):
            self.ui_hook.unhook()

        # Unregister actions
        for action in self.registered_actions:
            idaapi.unregister_action(action)

        if self.hexrays_inited:
            # Unregister hexrays actions
            for action in self.registered_hx_actions:
                idaapi.unregister_action(action)
            if self.hx_hook:
                idaapi.remove_hexrays_callback(self.hx_hook.callback)
            idaapi.term_hexrays_plugin()

def PLUGIN_ENTRY():
    return LazyIDA_t()


repository: kernullist-honey/stlcomment
file: src/stl_comment.py
description: STL Comment is an IDA plugin that comments on STL calls.
created_at: 2019-01-14 06:42:42+00:00
pushed_at: 2019-01-14 07:00:59+00:00
url:
https://github.com/kernullist-honey/stlcomment/blob/8579c12d0d8c0ba932dcd5309a33d41822dfcee4/src/stl_comment.py

content:
#!/usr/bin/python
# coding: utf-8
#
# stl_comment.py
#
import idaapi
import idautils
import idc
import re


class STLCommentHandler(idaapi.action_handler_t):
	def __init__(self):
		idaapi.action_handler_t.__init__(self)

	def activate(self, ctx):
		plg = STLCommentPlugin()
		plg.run(ctx)
		return 1

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

class STLCommentPlugin(idaapi.plugin_t):
	plg_name = 'STL Comment Plugin'
	act_name = 'Run ' + plg_name
	act_tooltip = act_name
	menu_category = 'Options/'

	flags = idaapi.PLUGIN_KEEP
	comment = plg_name
	help = ''
	wanted_name = plg_name
	wanted_hotkey = 'Ctrl-Alt-S'


	def init(self):
		idaapi.msg('[%s] init. press CTRL+ALT+S in a function.' % (self.plg_name))

		act_desc = idaapi.action_desc_t(
			self.act_name,
			self.plg_name,
			STLCommentHandler(),
			None,
			self.act_tooltip)

		idaapi.register_action(act_desc)
		idaapi.attach_action_to_menu(
			self.menu_category,
			self.act_name,
			idaapi.SETMENU_APP)

		return self.flags


	def term(self):
		idaapi.detach_action_from_menu(self.menu_category, self.act_name)
		idaapi.unregister_action(self.act_name)
		return None


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


	def get_current_function_range(self, current_ea):
		current_function_begin = idaapi.BADADDR
		current_function_end = idaapi.BADADDR
		for functionAddr in idautils.Functions():
			function_begin = idc.GetFunctionAttr(functionAddr, idc.FUNCATTR_START)
			function_end = idc.GetFunctionAttr(functionAddr, idc.FUNCATTR_END)
			if function_begin <= current_ea and function_end >= current_ea:
				current_function_begin = function_begin
				current_function_end = function_end
				break
		
		return current_function_begin, current_function_end


	def get_groomed_stl_functionnames(self):
		stl_names = dict()
		for functionAddr in idautils.Functions():
			functionName = idc.GetFunctionName(functionAddr)
			demangledName = idc.Demangle(functionName, idc.INF_SHORT_DN)
			if demangledName is None:
				continue
			if self.is_related_with_stl(demangledName) != True:
				continue			
			groomed_name = self.groom_stl(demangledName)
			if groomed_name == demangledName:
				continue
			
			stl_names[functionAddr] = groomed_name

		return stl_names


	def is_related_with_stl(self, demangledName):
		if re.search(r'\bstd\:\:', demangledName):
			return True
		return False


	def groom_template_phrase(self, string, pattern_strings):
		processed_string = string
		
		for pattern_string in pattern_strings:
			pattern = re.compile(pattern_string)

			while True:
				match = pattern.search(processed_string)
				if match == None:
					break

				index = match.span()
				matched_string = processed_string[index[0]:index[1]]

				open_bracket_cnt = matched_string.count('<')
				close_bracket_cnt = matched_string.count('>')
				i = 1
				while open_bracket_cnt > close_bracket_cnt:
					matched_string = processed_string[index[0]:index[1]+i]
					open_bracket_cnt = matched_string.count('<')
					close_bracket_cnt = matched_string.count('>')
					i += 1

				processed_string = processed_string.replace(matched_string, '')

		return processed_string


	def groom_stl_phrase(self, string, pattern_string, replace_string):
		processed_string = string
		result = re.sub(pattern_string, replace_string, processed_string)
		if result != processed_string:
			processed_string = result
		return processed_string


	def groom_stl(self, demangledName):
		template_patterns = [
			',?class std::allocator<[a-zA-Z: _,<]*>,?', 
			',?struct std::char_traits<[a-zA-Z: _,<]*>,?'
		]

		groomed_name = self.groom_template_phrase(demangledName, template_patterns)
		groomed_name = self.groom_stl_phrase(groomed_name, r'^public\:\s', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'^protected\:\s', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'^private\:\s', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'\bclass\s', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'\bstruct\s', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'\bstd\:\:', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'\b__cxx\d\d::', '')
		groomed_name = self.groom_stl_phrase(groomed_name, r'\(.*\)', '()')
		groomed_name = self.groom_stl_phrase(groomed_name, r'basic_string<char>', 'string')
		groomed_name = self.groom_stl_phrase(groomed_name, r'basic_string<wchar_t>', 'wstring')
		groomed_name = self.groom_stl_phrase(groomed_name, r'basic_string<unsigned short>', 'wstring')
		groomed_name = self.groom_stl_phrase(groomed_name, r'(\S+)::(~?)\1', r'\2\1')
		groomed_name = self.groom_stl_phrase(groomed_name, r'_Simple_types<(\w*)>', r'\1')
		groomed_name = self.groom_stl_phrase(groomed_name, r'_List_simple_types<(\w*)>', r'\1')

		return groomed_name


	def comment_stl(self):
		idaapi.msg('[%s] start to add comments\n' % (self.plg_name))

		current_ea = idc.here()
		if current_ea == idaapi.BADADDR:
			idaapi.warning('[%s] invalid address!\n' % (self.plg_name))
			return
		
		current_function_begin, current_function_end = self.get_current_function_range(current_ea)
		if current_function_begin == idaapi.BADADDR or current_function_end == idaapi.BADADDR:
			idaapi.warning('[%s] failed to get_current_function_range!\n' % (self.plg_name))
			return

		stl_names = self.get_groomed_stl_functionnames()
		if len(stl_names) == 0:
			idaapi.warning('[%s] not found any stl usages!\n' % (self.plg_name))
			return

		comments_count = 0

		for functionAddr in stl_names:
			xrefs = idautils.CodeRefsTo(functionAddr, False)
			for xref in xrefs:
				if xref >= current_function_begin and xref <= current_function_end:
					comment = stl_names[functionAddr]
					idaapi.msg('[%s] add a comment on %s (%s)\n' % (self.plg_name, hex(xref), comment))
					cfunc = idaapi.decompile(xref)
					tl = idaapi.treeloc_t()
					tl.ea = xref
					tl.itp = idaapi.ITP_BLOCK1
					cfunc.set_user_cmt(tl, comment)
					cfunc.save_user_cmts()
					idaapi.decompile(current_function_begin)
					comments_count += 1

		idaapi.msg('[%s] finished. %d commented\n' % (self.plg_name, comments_count))
		if comments_count > 0:
			idaapi.msg('[%s] now, press F5!\n' % (self.plg_name))
		
		return


def PLUGIN_ENTRY():
	return STLCommentPlugin()


repository: twisted-fun/IDARE
file: JumpTableFuncRename.py
description: Automation plugins for various reversing tasks in IDA.
wanted_name: IDARE: Jump Table Func Rename
created_at: 2021-09-05 07:19:58+00:00
pushed_at: 2021-09-07 20:46:33+00:00
url:
https://github.com/twisted-fun/IDARE/blob/60675316368be4fcb155d9b413803d4a383cb021/JumpTableFuncRename.py

content:
import idaapi
import ida_kernwin
from idare.utils import jump_table, rename_function


class jump_table_func_rename_t(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = ""
    help = ""
    wanted_name = "IDARE: Jump Table Func Rename"
    wanted_hotkey = ""

    def init(self):
        return idaapi.PLUGIN_OK

    def run(self, arg):
        # Get the jump table user selection
        selection, startaddr, endaddr = ida_kernwin.read_range_selection(None)
        if selection != 1:
            print("Err - Please provide a jump table selection.")
            return False
        print("Selection range: 0x{:x} - 0x{:x}".format(startaddr, endaddr))

        # Ask user for jump table member format to get the position
        # of string, function and overall size of the member
        member_format_input = ida_kernwin.ask_str(
            "string:4,junk:8,function:4", 1000, "Jump Table Member Format"
        )
        member_format = []
        for x in member_format_input.split(","):
            mem_type, num_bytes = x.split(":")
            num_bytes = int(num_bytes)
            member_format.append([mem_type, num_bytes])
        try:
            jt = jump_table(startaddr, endaddr, member_format)
        except:
            print("Err - Jump table selection and Member format mismatch.")
            return False

        for mem_string, mem_function_ea in jt.traverse():
            print("Renaming sub_{:X} -> fn_{}".format(mem_function_ea, mem_string))
            rename_function(mem_function_ea, "fn_" + mem_string)

    def term(self):
        pass


def PLUGIN_ENTRY():
    return jump_table_func_rename_t()


repository: 0xAlexei/WindowsDefenderTools
file: scripts/mp_apicall_7.py
description: Tools for instrumenting Windows Defender's mpengine.dll
wanted_name: MsMpEng_apicall
comment: MsMpEng apicall x86 Parser
created_at: 2018-07-15 20:21:57+00:00
pushed_at: 2018-10-25 03:26:46+00:00
url:
https://github.com/0xAlexei/WindowsDefenderTools/blob/772f7b1205ab8bdb4ca7000d9a0ea92ef07b6c14/scripts/mp_apicall_7.py

content:
"""
ONLY WORKS WITH IDA 7 API

Drop this in your plugins folder. The script is only invoked for files named *.mp.dll
"""
import idaapi
import idc
import struct
import ida_idp

"""
Based on Rolf Rolles' work documented at 
http://www.msreverseengineering.com/blog/2015/6/29/transparent-deobfuscation-with-ida-processor-module-extensions

The purpose of this module is to transform the custom mpengine API call instructions into actual valid X86
The instructions look like:

0f ff f0 [4 byte crc hash]

These instructions are kept in functions that invoke these specific API emulation routines. IDA chokes on
them and does not recognize full functions. By fixing them up we can create functions, and also automatically
label the functions with the name of the API they provide emulation for

loc_7C816D1A:                         
7C816D1A                                       
7C816D1A                 mov     edi, edi
7C816D1C                 call    $+5
7C816D21                 add     esp, 4
7C816D24                 db 0Fh
7C816D25                 db 0FFh
7C816D26                 db 0F0h
7C816D27                 dd 0D06B7F2Ah
7C816D2B                 retn    0Ch

NOTE: for some reason, lines with the apicall instruction come out blue rather than the default black. I looked into changing the color, but ended up leaving it - I like that these unique instructions pop out in the disassembly.

The following C code will generate hashes. DLL names are all caps and include ".DLL"

eg:

$ ./hash KERNEL32.DLL Sleep
KERNEL32.DLL!Sleep: 0x5779beb6

	#include <stdio.h>
	#include <stdint.h>
	#include <stdlib.h>
	#include <limits.h>

	//Taviso code from https://bugs.chromium.org/p/project-zero/issues/detail?id=1260&can=1&q=MsMpEng
	uint32_t crcstr(unsigned char *message) {
	   int i, j;
	   unsigned int byte, crc, mask;

	   i = 0;
	   crc = 0xFFFFFFFF;
	   while (message[i] != 0) {
	      byte = message[i];            // Get next byte.
	      crc = crc ^ byte;
	      for (j = 7; j >= 0; j--) {    // Do eight times.
	         mask = -(crc & 1);
	         crc = (crc >> 1) ^ (0xEDB88320 & mask);
	      }
	      i = i + 1;
	   }
	   return crc;
	}

	int main(int argc, char * argv[])
	{
	   if (argc < 3)
	   {
	      printf("usage ./hash DLLNAME_ALLCAPS.DLL FuncName\n");
	      exit(1);
	   }
	   uint32_t ApiCrc = crcstr((unsigned char *)argv[1]) ^ crcstr((unsigned char *)argv[2]);

	   printf("%s!%s: 0x%x\n", argv[1], argv[2], ApiCrc);
}

"""

hashesToNames = {3514167808L: 'KERNEL32_DLL_WinExec', 3018310659L: 'NTDLL_DLL_VFS_FindNextFile', 836405768L: 'KERNEL32_DLL_CreateProcessA', 2171982857L: 'NTDLL_DLL_NtSetContextThread', 146611723L: 'NTDLL_DLL_NtWaitForMultipleObjectsWorker_PreBlock', 3009652240L: 'NTDLL_DLL_NtGetContextThread', 2721006613L: 'KERNEL32_DLL_MpReportEventEx', 2569699353L: 'NTDLL_DLL_VFS_DeleteFileByHandle', 2363540508L: 'KERNEL32_DLL_SetFileAttributesA', 561219101L: 'NTDLL_DLL_VFS_CopyFile', 6236195L: 'NTDLL_DLL_NtSetEventWorker', 1165823525L: 'NTDLL_DLL_NtReleaseSemaphoreWorker', 935667244L: 'KERNEL32_DLL_VirtualAlloc', 3604202544L: 'KERNEL32_DLL_WriteProcessMemory', 3436587068L: 'NTDLL_DLL_VFS_SetLength', 2678794302L: 'NTDLL_DLL_NtDeleteFileWorker', 3836035660L: 'KERNEL32_DLL_ExitThread', 2420465236L: 'KERNEL32_DLL_VirtualAlloc', 1185677909L: 'KERNEL32_DLL_OpenProcess', 444799073L: 'NTDLL_DLL_MpUfsMetadataOp', 3297998953L: 'NTDLL_DLL_NtCreateEventWorker', 671954542L: 'KERNEL32_DLL_GetModuleHandleA', 695509624L: 'NTDLL_DLL_NtCreateSemaphoreWorker', 938333310L: 'ADVAPI32_DLL_RegOpenKeyExW', 551072383L: 'NTDLL_DLL_VFS_SetCurrentDir', 2858148586L: 'NTDLL_DLL_NtCreateThreadWorker', 111548048L: 'ADVAPI32_DLL_RegDeleteValueW', 852225171L: 'KERNEL32_DLL_TerminateProcess', 3271098004L: 'NTDLL_DLL_NtReadFileWorker', 1672903830L: 'KERNEL32_DLL_FlushFileBuffers', 2766673560L: '__t', 4143527585L: 'KERNEL32_DLL_MoveFileWWorker', 1895208615L: 'NTDLL_DLL_NtResetEventWorker', 2154630824L: 'KERNEL32_DLL_GetCurrentThread', 643813319L: '__t', 3505432946L: 'NTDLL_DLL_MpGetSelectorBase', 389600946L: 'NTDLL_DLL_VFS_SetAttrib', 1467596470L: 'KERNEL32_DLL_Sleep', 2994738363L: 'KERNEL32_DLL_OutputDebugStringA', 2436146366L: 'KERNEL32_DLL_GetModuleFileNameA', 1832462529L: 'NTDLL_DLL_NtCloseWorker', 3832320706L: 'NTDLL_DLL_NtReleaseMutantWorker', 1423484611L: 'KERNEL32_DLL_VirtualFree', 3473119430L: 'NTDLL_DLL_NtSetContextThread', 4158726861L: 'KERNEL32_DLL_MpAddToScanQueue', 3164325074L: 'KERNEL32_DLL_ExitProcess', 2362794197L: 'NTDLL_DLL_ThrdMgr_GetCurrentThreadHandle', 3035488987L: 'KERNEL32_DLL_GetCommandLineA', 4246072031L: 'KERNEL32_DLL_GetThreadContext', 1358384353L: 'KERNEL32_DLL_GetProcAddress', 161412834L: 'ADVAPI32_DLL_RegEnumKeyExW', 37968611L: 'NTDLL_DLL_NtResumeThreadWorker', 1361304293L: 'NTDLL_DLL_NtQueryInformationThreadWorker', 3267971814L: 'KERNEL32_DLL_GetTickCount', 2469586663L: '__t', 3616703208L: 'NTDLL_DLL_NtOpenThreadWorker', 689456874L: 'KERNEL32_DLL_CloseHandle', 3496705834L: 'KERNEL32_DLL_VirtualQuery', 2521119980L: 'NTDLL_DLL_NtQueryInformationFileWorker', 805468541L: 'KERNEL32_DLL_MpCreateMemoryAliasing', 2218737917L: 'KERNEL32_DLL_GetCurrentProcessId', 813909253L: 'NTDLL_DLL_VFS_FindFirstFile', 3776785163L: 'NTDLL_DLL_ThrdMgr_SwitchThreads', 3297990413L: 'KERNEL32_DLL_VirtualProtectEx', 1169409808L: 'NTDLL_DLL_ObjMgr_ValidateVFSHandle', 950361878L: 'USER32_DLL_MessageBoxA', 3087735069L: 'KERNEL32_DLL_SetFileTime', 2931759393L: 'NTDLL_DLL_NtCreateMutantWorker', 3547756835L: 'NTDLL_DLL_NtOpenMutantWorker', 47878950L: 'NTDLL_DLL_NtSetInformationFileWorker', 2943324456L: 'KERNEL32_DLL_MpReportEventW', 2902951210L: 'NTDLL_DLL_VFS_GetHandle', 2091420465L: 'NTDLL_DLL_VFS_UnmapViewOfFile', 3118073140L: 'ADVAPI32_DLL_RegSetValueExW', 2718092597L: 'ADVAPI32_DLL_RegCreateKeyExW', 4225249085L: 'NTDLL_DLL_NtSetLdtEntries', 1887913278L: 'NTDLL_DLL_VFS_MapViewOfFile', 2766101311L: 'NTDLL_DLL_VFS_Open', 1039809345L: 'NTDLL_DLL_VFS_GetLength', 1415316291L: 'NTDLL_DLL_ThrdMgr_SaveTEB', 4288048966L: 'ADVAPI32_DLL_RegEnumValueW', 3228482890L: 'NTDLL_DLL_VFS_DeleteFile', 122791243L: 'NTDLL_DLL_NtTerminateThreadWorker', 3960623330L: 'KERNEL32_DLL_CreateDirectoryW', 1190003539L: 'ADVAPI32_DLL_RegQueryValueExW', 1850539356L: 'KERNEL32_DLL_ReadProcessMemory', 1092269921L: 'KERNEL32_DLL_ExitProcess', 4253992806L: 'NTDLL_DLL_NtWaitForMultipleObjectsWorker_PostBlock', 1484319592L: 'KERNEL32_DLL_CreateToolhelp32Snapshot', 764461426L: 'KERNEL32_DLL_CreateFileMappingA', 3517602195L: 'NTDLL_DLL_NtSuspendThreadWorker', 2994557300L: 'KERNEL32_DLL_CopyFileWWorker', 2439823221L: '__t', 2383730555L: 'NTDLL_DLL_VFS_FindClose', 343092605L: 'KERNEL32_DLL_GetCurrentThreadId', 2911700366L: 'KERNEL32_DLL_VirtualFree', 2987103123L: 'NTDLL_DLL_NtDuplicateObjectWorker', 3125450133L: 'ADVAPI32_DLL_RegDeleteKeyW', 3939937694L: 'NTDLL_DLL_VFS_FileExists', 1589306273L: 'KERNEL32_DLL_RemoveDirectoryW', 3213227941L: 'KERNEL32_DLL_UnimplementedAPIStub', 2984009638L: 'NTDLL_DLL_NtPulseEventWorker', 1202720177L: 'NTDLL_DLL_VFS_MoveFile', 1225607610L: 'KERNEL32_DLL_GetCurrentProcess', 1386109890L: 'KERNEL32_DLL_LoadLibraryW', 1902764997L: 'KERNEL32_DLL_MpReportEvent', 1591092167L: 'NTDLL_DLL_NtCreateFileWorker', 1523256266L: 'NTDLL_DLL_NtWriteFileWorker', 556905933L: 'NTDLL_DLL_NtContinue', 3860121039L: 'NTDLL_DLL_VFS_GetAttrib', 3881168849L: 'KERNEL32_DLL_MpSetSelectorBase', 4283257812L: 'ADVAPI32_DLL_RegQueryInfoKeyW', 2914346968L: 'NTDLL_DLL_NtOpenSemaphoreWorker', 174967782L: 'NTDLL_DLL_NtOpenEventWorker', 4260291559L: 'NTDLL_DLL_VFS_Write', 2476646141L: 'NTDLL_DLL_NtControlChannel', 3536558576L: 'NTDLL_DLL_VFS_FlushViewOfFile', 2566429180L: 'NTDLL_DLL_VFS_Read', 421802495L: 'KERNEL32_DLL_ExitThread'}



NN_apicall = ida_idp.CUSTOM_INSN_ITYPE

class parse_apicall_hook(idaapi.IDP_Hooks):
	def __init__(self):
		idaapi.IDP_Hooks.__init__(self)

	def ev_ana_insn(self, insn):
		global hashesToNames

		insnbytes = idaapi.get_bytes(insn.ea, 3)
		if insnbytes == '\x0f\xff\xf0': 
			apicrc = idaapi.get_long(insn.ea+3)
			apiname = hashesToNames.get(apicrc)
			if apiname is None:
				print "ERROR: apicrc 0x%x NOT FOUND!"%(apicrc)
				#apiname = "UNKNOWN_APICALL"

			print "apicall: %s @ 0x%x"%(apiname, insn.ea)

			insn.itype = NN_apicall
			insn.Op1.type = idaapi.o_imm
			insn.Op1.value = apicrc
			insn.Op1.dtyp = idaapi.dt_dword
			insn.size = 7 #eat up 7 bytes
		
			return True 
		return False

	def ev_out_mnem(self, outctx):
		insntype = outctx.insn.itype

		if insntype == NN_apicall:
			mnem = "apicall"
			outctx.out_line(mnem)

			MNEM_WIDTH = 8
			width = max(1, MNEM_WIDTH - len(mnem))
			outctx.out_line(' ' * width)

			return True
		return False

	def ev_out_operand(self, outctx, op):
		insntype = outctx.insn.itype

		if insntype == NN_apicall:
			apicrc = op.value
			apiname = hashesToNames.get(apicrc)

			if apiname is None:
				return False
			else:
				s = apiname.split("_DLL_")
				operand_name = "!".join( [s[0].lower(), s[1]] )
			print "FOUND:", operand_name

			outctx.out_line(operand_name)

			return True
		return False

class apicall_parse_t(idaapi.plugin_t):
	flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
	comment = "MsMpEng apicall x86 Parser"
	wanted_hotkey = ""
	help = "Runs transparently during analysis"
	wanted_name = "MsMpEng_apicall"
	hook = None

	def init(self):
		self.hook = None
		if not ".mp.dll" in idc.GetInputFile() or idaapi.ph_get_id() != idaapi.PLFM_386:
			return idaapi.PLUGIN_SKIP

		print "\n\n-->MsMpEng apicall x86 Parser Invoked!\n\n"
				
		self.hook = parse_apicall_hook()
		self.hook.hook()
		return idaapi.PLUGIN_KEEP

	def run(self, arg):
		pass

	def term(self):
		if self.hook:
			self.hook.unhook()

def PLUGIN_ENTRY():
	return apicall_parse_t()


repository: techbliss/Processor-Changer
file: plugins/Processor-Changer.py
description: Tool to change processor inside ida
wanted_name: Processor Changer
comment: This is a comment
created_at: 2014-04-18 15:32:06+00:00
pushed_at: 2014-08-11 21:05:25+00:00
url:
https://github.com/techbliss/Processor-Changer/blob/075671503b2f531933df011073860a681b040c36/plugins/Processor-Changer.py

content:
import re
import idaapi
import idc
from idc import *
from idaapi import *
import PyQt4
from PyQt4 import QtCore, QtGui
import idautils

class Zadow(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "This is a comment"

    help = "Processor Changer"
    wanted_name = "Processor Changer"
    wanted_hotkey = "Alt-F7"

    def init(self):
        idaapi.msg("Process Plugin is found. \n")
        return idaapi.PLUGIN_OK

    def run(self, arg):
        idaapi.msg("run() called with %d!\n" % arg)

    def term(self):
        idaapi.msg("")
    
    def AddMenuElements(self):
        '''Menus are better than no GUI at all *sigh*'''

        idaapi.add_menu_item("Debugger/", "Change Processor to SPU", "", 0, self.ZadowSpu, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to PPC", "", 0, self.ZadowPpc, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to PPC64", "", 0, self.ZadowPpc64, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to Arm", "", 0, self.ZadowArm, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to PC", "", 0, self.ZadowPc, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to java", "", 0, self.ZadowJava, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to Mips", "", 0, self.ZadowMips, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to Net", "", 0, self.ZadowNet, ())
        idaapi.add_menu_item("Debugger/", "Change Processor to Dalvik", "", 0, self.ZadowDalvik, ())
        idaapi.add_menu_item("Debugger/", "HighLight Calls", "", 0, self.ZadowHigh, ())



    def run(self, arg = 0):
        idaapi.msg("Hombre you are runnig good.\n")

        self.AddMenuElements()

    def ZadowSpu(self):
        idc.SetProcessorType('spu', SETPROC_USER)

    def ZadowPpc(self):
        idc.SetProcessorType('ppc', SETPROC_USER)

    def ZadowPpc64(self):
        idc.SetProcessorType('ppc64', SETPROC_USER)

    def ZadowArm(self):
        idc.SetProcessorType('arm', SETPROC_USER)
    def ZadowPc(self):
        idc.SetProcessorType('metapc', SETPROC_USER), idaapi.load_and_run_plugin("hexrays.plw", 0)

    def ZadowJava(self):
        idc.SetProcessorType('java', SETPROC_USER)

    def ZadowMips(self):
        idc.SetProcessorType('mips', SETPROC_USER)

    def ZadowNet(self):
        idc.SetProcessorType('cli', SETPROC_USER)

    def ZadowDalvik(self):
        idc.SetProcessorType('dalvik', SETPROC_USER)

    def ZadowHigh(self):
        from idautils import XrefsFrom
        from idaapi import fl_CN as call_near, fl_CF as call_far
        from providers import ida

        provider = ida.IDA()

        startEA = provider.funcStart(provider.currentEA())
        endEA = provider.funcEnd(provider.currentEA())

        all_addresses = list(provider.iterInstructions(startEA, endEA))
        all_addresses.extend(provider.iterFuncChunks(startEA))
        all_addresses = list(set(all_addresses))

        for head in all_addresses:
            for xref in XrefsFrom(head):
                if xref.type == call_near or xref.type == call_far:
                    provider.setColor(head, 0x0000FF)

                provider.refreshView()


def PLUGIN_ENTRY():
    return Zadow()


repository: sfinktah/sfida
file: keypatch.py
description: idapro python toolkit
url: https://github.com/sfinktah/sfida/blob/b9a8307b5c3e3cbfeb2372757a0b3d0da84142ea/keypatch.py
wanted_name: Keypatch Patcher
comment: Keypatch plugin for IDA Pro (using Keystone framework)
created_at: 2021-03-01 10:05:12+00:00
pushed_at: 2024-10-23 03:13:14+00:00
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 *
del debug
import idc
import idaapi
import six

################################ IDA 6/7 Compatibility import ###########################################
if idaapi.IDA_SDK_VERSION >= 700:
    import ida_search
    from idc import (
        get_operand_type,
        print_operand,
        get_item_end,
        get_item_head,
        get_item_size,
        get_sreg,
        has_value,
        get_full_flags,
        get_wide_byte,
        get_func_attr,
        set_func_end,
        warning,
        get_screen_ea
    )
    from idaapi import (
        get_bytes,
        plan_and_wait,
        is_mapped,
        Choose,
        jumpto
    )
else:
    from idc import (
        GetOpType as get_operand_type,
        GetOpnd as print_operand,
        ItemEnd as get_item_end,
        ItemHead as get_item_head,
        ItemSize as get_item_size,
        GetReg as get_sreg,
        hasValue as has_value,
        GetFlags as get_full_flags,
        Byte as get_wide_byte,
        GetFunctionAttr as get_func_attr,
        Jump as jumpto,
        Warning as warning,
        ScreenEA as get_screen_ea
    )
    from idaapi import (
        get_many_bytes as get_bytes,
        analyze_area as plan_and_wait,
        isEnabled as is_mapped,
        func_setend as set_func_end,
        Choose2 as Choose
    )
#########################################################################################################

# 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 = []

################################ Python2/3 Compatibility function #######################################
# Convert unicode/string/bytes to string
def to_string(s):
  # python3 bytes
  if six.PY3 and isinstance(s, bytes):
      return s.decode('latin-1')
  # python2 unicode
  elif six.PY2 and isinstance(s, six.text_type):
      return s.encode('utf-8')
  return str(s)

def to_hexstr(buf, sep=' '):
    # for python3 bytes
    if six.PY3 and isinstance(buf, bytes):
        return sep.join("{0:02x}".format(c) for c in buf).upper()
    return sep.join("{0:02x}".format(ord(c)) for c in buf).upper()
#########################################################################################################

################################ IDA 6/7 Compatibility function #########################################
def get_dtype(ea, op_idx):
    if idaapi.IDA_SDK_VERSION >= 700:
        insn = idaapi.insn_t()
        idaapi.decode_insn(insn, ea)
        dtype = insn.ops[op_idx].dtype
        dtyp_size = idaapi.get_dtype_size(dtype)
    else:
        dtype = idaapi.cmd.Operands[op_idx].dtyp
        dtyp_size = idaapi.get_dtyp_size(dtype)
    return dtype, dtyp_size

def set_comment(ea, comment):
    if idaapi.IDA_SDK_VERSION >= 700:
        idc.set_cmt(ea, comment, 0)
    else:
        idc.MakeComm(ea, comment)

def get_comment(ea):
    if idaapi.IDA_SDK_VERSION >= 700:
        return idc.get_cmt(ea, 0)
    return idc.Comment(ea)

def read_range_selection():
    if idaapi.IDA_SDK_VERSION >= 700:
        return idaapi.read_range_selection(None)
    return idaapi.read_selection()
#########################################################################################################

# 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):
    # Import for python2
    try:
        from urllib2 import Request, urlopen, URLError, HTTPError
    except:
        from urllib.request import Request, urlopen
        from urllib.error import 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 as e:
        # print "HTTP Error:", e.code , url
        # fail to download this file
        return (1, None)
    except URLError as 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
    """
    name = to_string(name)
    (typ, value) = idaapi.get_name_value(_from, name)
    if typ == idaapi.NT_BYTE:  # typ is byte name (regular name)
        value = idaapi.get_name_ea(_from, name)
    return (typ, 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 is_mapped(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]

                (typ, value) = _get_name_value(address, sym)

                # skip if name doesn't exist or segment / segment registers
                if typ 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 != get_item_head(address):
            return (None, 0)

        size = get_item_size(address)
        item = get_bytes(address, size)

        if item is None:
            return (None, 0)

        if hex_output:
            item = to_hexstr(item)

        return (item, size)

    @staticmethod
    def get_op_dtype_name(ea, 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, dtyp_size = get_dtype(ea, op_idx)
        if dtype == idaapi.dt_tbyte and dtyp_size == 10:
          return 'xword'

        dtyp_name = dtyp_lists.get(dtype, 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 + get_item_size(start)

        return codes

    # get disasm from IDA
    # return '' on invalid address
    def ida_get_disasm(self, address, fixup=False):

        def IdaGetMnem(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 != get_item_head(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 = IdaGetMnem(asm)
        if mnem == '' or mnem in ('rep', 'repne', 'repe'):
            return asm

        opers = []
        while get_operand_type(address, i) > 0 and i < 6:
            t = get_operand_type(address, i)
            o = print_operand(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(address, 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 get_sreg(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, size):
        ea = address
        orig_data = ''

        while ea < (address + size):

            if not has_value(get_full_flags(ea)):
                print("Keypatch: FAILED to read data at 0x{0:X}".format(ea))
                break

            orig_byte = get_wide_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, size):
        # save original function end to fix IDA re-analyze issue after patching
        orig_func_end = get_func_attr(address, idc.FUNCATTR_END)

        (patched_len, orig_data) = self.patch_raw(address, patch_data, size)

        if size != 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
            plan_and_wait(address, address + patched_len + 1)
        else:
            plan_and_wait(address, orig_func_end)

            # try to fix IDA function re-analyze issue after patching
            set_func_end(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 = get_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 = get_item_end(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)
                set_comment(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, '')
                set_comment(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 = get_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)
            set_comment(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):
        l = sorted(list(dictionary.items()), key=lambda t: t[0], reverse=False)
        keys = [i[0] for i in l]
        values = [i[1] for i in l]

        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:
                is_mapped(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 as 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 = list(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 = list(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(Choose):
    def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False):
        super(SearchResultChooser, self).__init__(
            title,
            [["Address", Choose.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
        jumpto(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 = list(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()

    ################################ IDA 6/7 Compatibility method #######################################
    def find_binary(self, ea):
        if idaapi.IDA_SDK_VERSION >= 700:
            return ida_search.find_binary(ea, idaapi.cvar.inf.max_ea, self.GetControlValue(self.c_encoding), 16, idc.SEARCH_DOWN)
        return idc.FindBinary(ea, idc.SEARCH_DOWN, self.GetControlValue(self.c_encoding))
    #####################################################################################################

    # 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 = self.find_binary(address)
                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 Exception as e:
                # 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)
        content = to_string(content)
        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
            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?
            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 = get_screen_ea()
        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:
            warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!")
            return

        selection, addr_begin, addr_end = read_range_selection()
        if selection:
            # call Fill Range function on this selected code
            return self.fill_range()

        address = get_screen_ea()

        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:
                            warning("ERROR: Keypatch found invalid assembly [{0}]".format(assembly))
                        elif length == -1:
                            warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(address))
                        elif length == -2:
                            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:
            warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!")
            return
               
        selection, addr_begin, addr_end = read_range_selection()
        if not selection:
            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:
                    warning("ERROR: Keypatch failed to process this input.")
                    print("Keypatch: FAILED to process this input '{0}'".format(assembly))
                elif length == -1:
                    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()


repository: bootleg/ret-sync
file: ext_ida/SyncPlugin.py
wanted_name: ret-sync
comment: Reverse-Engineering Tools synchronization, ret-sync .
created_at: 2016-01-10 17:36:12+00:00
pushed_at: 2024-02-29 07:08:34+00:00
description:
ret-sync is a set of plugins that helps to synchronize a debugging session (WinDbg/GDB/LLDB/OllyDbg2/x64dbg) with IDA/Ghidra/Binary Ninja disassemblers.

url:
https://github.com/bootleg/ret-sync/blob/a6d0b53826d0dc5a075229ce5efe9004f834339e/ext_ida/SyncPlugin.py

content:
#
# Copyright (C) 2016-2021, Alexandre Gazet.
#
# Copyright (C) 2012-2015, Quarkslab.
#
# This file is part of ret-sync.
#
# ret-sync 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/>.
#

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import re
import sys
import time
import traceback
import struct
import binascii
import base64
import socket
import json
import uuid
import argparse

from retsync.syncrays import Syncrays
import retsync.rsconfig as rsconfig
from retsync.rsconfig import rs_encode, rs_decode, rs_log, rs_debug, load_configuration

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QProcess, QProcessEnvironment

import idc
import idaapi
import idautils
import ida_bytes
import ida_graph
import ida_range
import ida_funcs
import ida_name
import ida_hexrays
import ida_kernwin
import ida_idaapi
import ida_dbg
import ida_nalt

from idaapi import PluginForm


# get PYTHON_PATH settings, based on platform
PYTHON_PATH = rsconfig.get_python_interpreter()
os.environ['PYTHON_PATH'] = PYTHON_PATH

# default value is current script's path
BROKER_PATH = os.path.join(os.path.normpath(os.path.dirname(__file__)), rsconfig.PLUGIN_DIR, 'broker.py')
if not os.path.exists(BROKER_PATH):
    rs_log("[-] broker path is not properly set, current value: <%s>" % BROKER_PATH)
    raise RuntimeError

os.environ['IDB_PATH'] = os.path.dirname(os.path.realpath(idaapi.get_path(idaapi.PATH_TYPE_IDB)))

COL_CBTRACE = rsconfig.COL_CBTRACE


# --------------------------------------------------------------------------


class RequestHandler(object):

    # color callback
    def cb_color(self, ea):
        idaapi.set_item_color(ea, COL_CBTRACE)

    # instruction step callback
    def cb_curline(self, ea):
        if self.prev_loc:
            prev_ea, prev_color = self.prev_loc
            cur_color = idaapi.get_item_color(prev_ea)
            # race condition: block/instruction's color may have been modified
            # after it was saved
            if (cur_color != prev_color) and (cur_color != rsconfig.COL_CURLINE):
                prev_color = cur_color
            idaapi.set_item_color(prev_ea, prev_color)

        self.prev_loc = [ea, idaapi.get_item_color(ea)]
        idaapi.set_item_color(ea, rsconfig.COL_CURLINE)

    def cb_restore_last_line(self):
        if self.prev_loc:
            ea, col = self.prev_loc
            idaapi.set_item_color(ea, col)

    # support -a / --address switch
    def addr_switch(self, offset, msg):
        if (not msg) or (msg == ''):
            return [offset, msg]

        try:
            args = self.parser.parse_args(msg.split())
        except argparse.ArgumentError:
            rs_log('failed to parse command')
            return [None, msg]

        # no address switch supplied
        if not args.address:
            return [offset, msg]

        try:
            addr = int(''.join(args.address), 16)
        except (TypeError, ValueError):
            rs_log('failed to parse address, should be hex')
            return [None, msg]

        # make sure the address points to a valid instruction/data
        head = idaapi.get_item_head(addr)
        if head != addr:
            rs_log("ambiguous address, did you mean 0x%x ?" % head)
            return [None, msg]

        return [addr, ' '.join(args.msg)]

    # check if address is within a valid segment
    def is_safe(self, offset):
        return not (idc.get_segm_start(offset) == ida_idaapi.BADADDR)

    # rebase (and update) address with respect to local image base
    def rebase(self, base, offset):
        if base is not None:
            # check for non-compliant debugger client
            if base > offset:
                rs_log('unsafe addr: 0x%x > 0x%x' % (base, offset))
                return None

            # update base address of remote module
            if self.base_remote != base:
                self.base_remote = base

            offset = self.rebase_local(offset)

        if not self.is_safe(offset):
            rs_log('unsafe addr: 0x%x not in valid segment' % (offset))
            return None

        return offset

    # rebase address with respect to local image base
    def rebase_local(self, offset):
        if not (self.base == self.base_remote):
            offset = (offset - self.base_remote) + self.base

        return offset

    # rebase address with respect to remote image base
    def rebase_remote(self, offset):
        if not (self.base == self.base_remote):
            offset = (offset - self.base) + self.base_remote

        return offset

    # demangle names
    def demangle(self, name):
        mask = idc.get_inf_attr(idc.INF_SHORT_DEMNAMES)
        demangled = idc.demangle_name(name, mask)
        if demangled is None:
            return name
        else:
            return demangled

    # prevent flooding debug engine with too much commands
    # sync plugin does NOT wait for any sort of ack
    # example: "^ Debuggee already running error in 'g'"
    def notice_anti_flood(self):
        time.sleep(0.1)

    # append comment and handle cmt's size limitation (near 1024)
    def append_cmt(self, ea, cmt, rptble=False):
        if len(cmt) > 1024:
            rs_log("warning, comment needs to be splitted (from 0x%x)" % ea)
            nh = idaapi.next_head(ea, ida_idaapi.BADADDR)
            if nh == ida_idaapi.BADADDR:
                rs_log('[x] failed to find next instruction candidate')
                return

            self.append_cmt(nh, cmt[1024:], rptble)
            cmt = cmt[:1024]

        idaapi.append_cmt(ea, cmt, rptble)

    # location request, update disassembly IDA view
    def req_loc(self, hash):
        offset, base = hash['offset'], hash.get('base')
        ea = self.rebase(base, offset)
        if not ea:
            return

        if self.color:
            self.cb_color(ea)

        idaapi.jumpto(ea)
        self.cb_curline(ea)
        self.gm.center()

        if self.hexsync.enabled:
            self.hexsync.cb_loc(ea)

    # set remote base on purpose
    def req_rbase(self, hash):
        rbase = hash['rbase']
        self.base_remote = rbase

    # log command output request at addr
    def req_cmd(self, hash):
        msg_b64, offset, base = hash['msg'], hash['offset'], hash['base']
        msg = rs_decode(base64.b64decode(msg_b64))
        ea = self.rebase(base, offset)
        if not ea:
            return

        rs_log("cmd output added at 0x%x" % ea)
        self.append_cmt(ea, str(msg))

    # reset comment at addr
    def req_rcmt(self, hash):
        msg, offset, base = hash['msg'], hash['offset'], hash['base']
        offset, msg = self.addr_switch(offset, msg)
        if not offset:
            return

        ea = self.rebase(base, offset)
        if not ea:
            return

        idaapi.set_cmt(ea, str(''), False)
        rs_log("reset comment at 0x%x" % ea)

    # add comment request at addr
    def req_cmt(self, hash):
        msg, offset, base = hash['msg'], hash['offset'], hash['base']
        offset, msg = self.addr_switch(offset, msg)
        if not offset:
            return

        ea = self.rebase(base, offset)
        if not ea:
            return

        self.append_cmt(ea, str(msg))
        rs_log("comment added at 0x%x" % ea)

    # add a function comment at addr
    def req_fcmt(self, hash):
        msg, offset, base = hash['msg'], hash['offset'], hash['base']
        offset, msg = self.addr_switch(offset, msg)
        if not offset:
            return

        ea = self.rebase(base, offset)
        if not ea:
            return

        func = idaapi.get_func(ea)
        if not func:
            rs_log("could not find func for 0x%x" % ea)
            return

        idaapi.set_func_cmt(func, str(msg), False)
        rs_log("function comment added at 0x%x" % ea)

    # add an address comment request at addr
    def req_raddr(self, hash):
        raddr, rbase, offset, base = hash['raddr'], hash['rbase'], hash['offset'], hash['base']
        ea = self.rebase(base, offset)
        if not ea:
            return

        if self.base_remote != rbase:
            rs_log('could not rebase this address, 0x%x != 0x0, not in module')
            return

        addr = self.rebase(rbase, raddr)
        if not addr:
            return

        self.append_cmt(ea, "0x%x (rebased from 0x%x)" % (addr, raddr))
        rs_log("comment added at 0x%x" % ea)

    # return current cursor in IDA Pro
    def req_cursor(self, hash):
        rs_log('request IDA Pro cursor position')
        addr = self.rebase_remote(idc.get_screen_ea())
        self.notice_broker('cmd', "\"cmd\":\"0x%x\"" % addr)
        return

    # patch memory at specified address using info from debugger
    def req_patch(self, hash):
        addr, value, length = hash['addr'], hash['value'], hash['len']

        if length == 4:
            prev_value = idc.get_wide_dword(addr)
            if not ida_bytes.create_data(ea, FF_DWORD, 4, ida_idaapi.BADADDR):
                rs_log('[x] ida_bytes.create_data FF_DWORD failed')
            if not ida_bytes.patch_dword(addr, value):
                rs_log('[x] patch_dword failed')
            if not idc.op_plain_offset(addr, 0, 0):
                rs_log('[x] op_plain_offset failed')

        elif length == 8:
            prev_value = idc.get_qword(addr)
            if not ida_bytes.create_data(addr, FF_QWORD, 8, ida_idaapi.BADADDR):
                rs_log('[x] ida_bytes.create_data FF_QWORD failed')
            if not ida_bytes.patch_qword(addr, value):
                rs_log('[x] patch_qword failed')
            if not idc.op_plain_offset(addr, 0, 0):
                rs_log('[x] op_plain_offset failed')

        else:
            rs_log("[x] unsupported length: %d" % length)
            return

        rs_log("patched 0x%x = 0x%x (previous was 0x%x)" % (addr, value, prev_value))

    # return idb's symbol for a given address
    def req_rln(self, hash):
        raddr = hash['raddr']

        rs_debug("rln: 0x%x" % raddr)

        addr = self.rebase_local(raddr)
        if not addr:
            rs_log("could not rebase this address (0x%x)" % raddr)
            return

        sym = idaapi.get_func_name(addr)
        if sym:
            sym = self.demangle(sym)
            func = idaapi.get_func(addr)
            if not func:
                rs_log("could not find func for 0x%x" % addr)
                return

            lck = idaapi.lock_func(func)
            limits = ida_range.range_t()
            rs = ida_range.rangeset_t()

            if ida_funcs.get_func_ranges(rs, func) != ida_idaapi.BADADDR:
                limits.start_ea = rs.begin().start_ea
                limits.end_ea = rs.begin().end_ea

                if limits.start_ea != addr:
                    if (addr > limits.start_ea):
                        sym = "%s%s0x%x" % (sym, "+", addr - limits.start_ea)
                    else:
                        sym = "%s%s0x%x" % (sym, "-", limits.start_ea - addr)
            lck = None
        else:
            sym = idc.get_name(addr, ida_name.GN_VISIBLE)
            if sym:
                sym = self.demangle(sym)

        if sym:
            self.notice_broker('cmd', "\"cmd\":\"%s\"" % sym)
            rs_debug("resolved symbol: %s" % sym)
        else:
            rs_log("could not resolve symbol for address 0x%x" % addr)

    # return address for a given idb's symbol
    def req_rrln(self, hash):
        sym = hash['sym']
        rs_log("rrln> symbol \"%s\"" % sym)

        addr = idc.get_name_ea_simple(str(sym))
        if addr:
            raddr = self.rebase_remote(addr)
            self.notice_broker("cmd", "\"cmd\":\"%s\"" % raddr)
            rs_log("rrln> remote: 0x%x, local: 0x%x)" % (raddr, addr))
        else:
            rs_log("rrln> symbol not found \"%s\"" % sym)

    # add label request at addr
    def req_lbl(self, hash):
        msg, offset, base = hash['msg'], hash['offset'], hash['base']
        offset, msg = self.addr_switch(offset, msg)
        if not offset:
            return

        ea = self.rebase(base, offset)
        if not ea:
            return

        flags = False
        if str(msg).startswith('@@'):
            flags = idaapi.SN_LOCAL

        idaapi.set_name(ea, str(msg), flags)
        rs_log("label added at 0x%x" % ea)

    # color request at addr
    def req_bc(self, hash):
        global COL_CBTRACE
        msg, offset, base = hash['msg'], hash['offset'], hash['base']

        if self.is_active:
            ea = self.rebase(base, offset)
            if not ea:
                return
        else:
            ea = self.base

        if (msg == 'oneshot'):
            rs_log("color oneshot added at 0x%x" % ea)
            # mark address as being colored
            self.prev_loc = [ea, COL_CBTRACE]
        elif (msg == 'on'):
            rs_log("color start from 0x%x" % ea)
            self.color = True
            self.prev_loc = [ea, COL_CBTRACE]
        elif (msg == 'off'):
            rs_log("color end at 0x%x" % ea)
            self.color = False
        elif (msg == 'set'):
            new_col = hash['rgb']
            if new_col > 0xffffff:
                rs_log('restoring color')
                new_col = rsconfig.COL_GREEN

            COL_CBTRACE = new_col
            rs_log("set color to 0x%x" % COL_CBTRACE)
        else:
            rs_log("invalid color request (%s)" % msg)

    # reload .bpcmds from idb
    def req_bps_get(self, hash):
        rs_log('[-] reload .bpcmds')
        node = idaapi.netnode(rsconfig.NETNODE_INDEX)
        if not node:
            rs_log('[-] failed to open netnode store')
            self.notice_broker("cmd", "\"cmd\":\"no blob\"")
            return

        node.create(rsconfig.NETNODE_STORE)
        blob = rs_decode(node.getblob(0, str(chr(1))))

        if not blob:
            rs_log('  -> no blob')
            self.notice_broker('cmd', "\"cmd\":\"    -> reloading .bpcmds: no blob\"")
            return

        self.notice_broker('cmd', "\"cmd\":\"%s\"" % blob)
        return

    # save .bpcmds to idb
    def req_bps_set(self, hash):
        blob = hash['msg']
        rs_log('[-] save .bpcmds')
        node = idaapi.netnode(rsconfig.NETNODE_INDEX)
        if not node:
            rs_log('[-] failed to open netnode store')
            self.notice_broker('cmd', "\"cmd\":\"    -> failed to save .bpcmds")
            return

        new = node.create(rsconfig.NETNODE_STORE)
        if new == 0:
            rs_log('    -> creating new netnode store')

        out = node.setblob(rs_encode(blob), 0, chr(1))
        self.notice_broker("cmd", "\"cmd\":\"    -> .bpcmds saved\"")
        return

    # compare loaded module md5 with idb's input file md5
    def req_modcheck(self, hash):
        md5, pdb = hash.get('md5'), hash.get('pdb')
        remote = None

        if md5:
            rs_log("modcheck idb (md5)")
            local = rs_decode(binascii.hexlify(idaapi.retrieve_input_file_md5())).upper()
            remote = (''.join(md5.split())).upper()
        elif pdb:
            rs_log("modcheck idb (pdb guid)")
            msg = rs_decode(base64.b64decode(pdb))
            local = DbgDirHlpr.read_rsds_guid()
            remote = DbgDirHlpr.parse_itoldyouso_output(msg)

        rs_log("    -> remote: <%s>" % remote)
        rs_log("    -> local : <%s>" % local)

        if remote == '0':
            output = '[!] warning, no Debug Directory'
        elif local == remote:
            output = '[+] module successfully matched'
        else:
            output = '[!] warning, modules mismatch'

        rs_log(output)
        self.notice_broker("cmd", "\"cmd\":\"%s\"" % output)
        return

    # specify debugger dialect used to send commands
    def req_set_dbg_dialect(self, hash):
        global SyncForm
        dialect = hash['dialect']
        if dialect in rsconfig.DBG_DIALECTS:
            self.dbg_dialect = rsconfig.DBG_DIALECTS[dialect]
            rs_log("set debugger dialect to %s, enabling hotkeys" % dialect)
            SyncForm.init_hotkeys()
        else:
            SyncForm.uninit_hotkeys()

    # request from broker
    def req_broker(self, hash):
        subtype = hash['subtype']

        if (subtype == 'msg'):
            # simple message announcement
            rs_log("<< broker << %s" % hash['msg'])

        elif(subtype == 'notice'):
            # notice from broker
            self.broker_port = int(hash['port'])
            rs_debug("<< broker << binding on port %d" % self.broker_port)

            for attempt in range(rsconfig.CONNECT_BROKER_MAX_ATTEMPT):
                try:
                    host = socket.gethostbyname('localhost')
                    self.broker_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self.broker_sock.settimeout(2)
                    self.broker_sock.connect((host, self.broker_port))
                    break
                except socket.error:
                    rs_log('failed to connect to broker')
                    rs_log(sys.exc_info())
                    if self.broker_sock:
                        self.broker_sock.close()
                    self.broker_sock = None
                    time.sleep(0.1)
                    if (attempt == (rsconfig.CONNECT_BROKER_MAX_ATTEMPT - 1)):
                        self.announcement("[sync] failed to connect to broker (attempt %d)" % attempt)
                        raise RuntimeError

            # request broker to validate its beacon
            time.sleep(0.4)
            self.beacon_notice()

        # enable/disable idb, if disable it drops most sync requests
        elif(subtype == 'enable_idb'):
            self.is_active = True
            rs_log('idb is enabled')

        elif(subtype == 'disable_idb'):
            self.is_active = False
            self.base_remote = None
            self.cb_restore_last_line()
            rs_log('idb is disabled')

    # parse and execute request
    # Note that sometimes we don't receive the whole request from the broker.py
    # so parsing fails. One way for fixing this would be to fix broker.py to get
    # everything until "\n" before proxying it but the way we do here is to read
    # everything until "}" is received (end of json)
    def parse_exec(self, req):
        if self.prev_req:
            if self.prev_req != "":
                if rsconfig.DEBUG_JSON:
                    rs_log("JSON merge with request: \"%s\"" % req)

            req = self.prev_req + req
            self.prev_req = ""
        if req == '':
            return
        if rsconfig.DEBUG_JSON:
            rs_log("parse_exec -> " + str(req))

        if not (req.startswith('[sync]')):
            rs_log("[<] bad hdr %s" % repr(req))
            rs_log('[-] Request dropped due to bad header')
            return

        req_ = self.normalize(req, 6)

        try:
            hash = json.loads(req_)
        except ValueError:
            if rsconfig.DEBUG_JSON:
                rs_log("[x] Sync failed to parse json\n '%s'. Caching for next req..." % req_)
                rs_log("------------------------------------")
            self.prev_req = req
            return

        rtype = hash['type']
        if rtype not in self.req_handlers:
            rs_log("unknown request: %s" % rtype)
            return

        req_handler = self.req_handlers[rtype]

        # few requests are handled even though idb is not enable
        if rtype in ['broker', 'dialect', 'bc']:
            req_handler(hash)
        else:
            if self.is_active:
                req_handler(hash)
            else:
                rs_debug("[-] Drop the %s request because idb is not enabled" % rtype)
                return

        idaapi.refresh_idaview_anyway()

    def normalize(self, req, taglen):
        req = req[taglen:]
        req = req.replace("\\", "\\\\")
        req = req.replace("\n", "")
        return req.strip()

    # send a kill notice to the broker (then forwarded to the dispatcher)
    def kill_notice(self):
        self.notice_broker("kill")

    # send a beacon notice to the broker
    def beacon_notice(self):
        self.notice_broker('beacon')

    # send a bp command (F2) to the debugger (via the broker and dispatcher)
    def bp_notice(self, oneshot=False):
        if not self.is_active:
            rs_log("idb isn't enabled, bp can't be set")
            return

        ea = idaapi.get_screen_ea()
        offset = self.rebase_remote(ea)
        cmd = "%s0x%x" % (self.dbg_dialect['bp1' if oneshot else 'bp'], offset)

        if (oneshot and 'oneshot_post' in self.dbg_dialect):
            cmd += self.dbg_dialect['oneshot_post']

        self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
        rs_log(">> set %s" % cmd)

    # send a hardware bp command (Ctrl-F2) to the debugger (via the broker and dispatcher)
    def hbp_notice(self, oneshot=False):
        if not self.is_active:
            rs_log("idb isn't enabled, hbp can't be set")
            return

        ea = idaapi.get_screen_ea()
        offset = self.rebase_remote(ea)
        cmd = "%s0x%x" % (self.dbg_dialect['hbp1' if oneshot else 'hbp'], offset)

        self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
        rs_log(">> set %s" % cmd)

    # send a oneshot bp command (F3) to the debugger (via the broker and dispatcher)
    def bp_oneshot_notice(self):
        self.bp_notice(True)

    # send a oneshot hardware bp command (Ctrl-F3) to the debugger (via the broker and dispatcher)
    def hbp_oneshot_notice(self):
        self.hbp_notice(True)

    # export IDB's breakpoint (Ctrl-F1) to the debugger (via the broker and dispatcher)
    def export_bp_notice(self):
        if not self.dbg_dialect:
            rs_log("idb isn't synced yet, can't export bp")
            return

        is_windbg = (self.dbg_dialect == 'windbg')

        # Windbg supports relative address, ie. mod+0xCAFE
        # for non relative address the remote base address is needed
        if (not is_windbg) and (not self.base_remote):
            rs_log("idb isn't enabled, can't export bp")
            return

        mod = self.name.split('.')[0].strip()
        nbp = ida_dbg.get_bpt_qty()

        for i in range(nbp):
            ea = idc.get_bpt_ea(i)
            attrs = [idc.BPTATTR_TYPE, idc.BPTATTR_COND, idc.BPTATTR_FLAGS]
            btype, cond, flags = [idc.get_bpt_attr(ea, x) for x in attrs]

            if cond:
                rs_log("bp %d: conditional bp not supported" % i)
            else:
                if ((btype in [idc.BPT_EXEC, idc.BPT_SOFT]) and
                   ((flags & idc.BPT_ENABLED) != 0)):

                    bp = self.dbg_dialect['hbp' if (btype == idc.BPT_EXEC) else 'bp']

                    if is_windbg:
                        offset = ea - self.base
                        cmd = "%s%s+0x%x" % (bp, mod, offset)
                    else:
                        offset = self.rebase_remote(ea)
                        cmd = "%s0x%x" % (bp, offset)

                    self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
                    rs_log("bp %d: %s" % (i, cmd))

        rs_log('export done')

    # send a translate command (Alt-F2) to the debugger (via the broker and dispatcher)
    def translate_notice(self):
        if not self.dbg_dialect:
            rs_log("idb isn't synced yet, can't translate")
            return

        ea = idaapi.get_screen_ea()
        mod = self.name.split('.')[0].strip()
        cmd = self.dbg_dialect['prefix'] + "translate 0x%x 0x%x %s" % (self.base, ea, mod)
        self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
        rs_debug("translate address 0x%x" % ea)

    # send a command to the debugger (via the broker and dispatcher)
    def cmd_notice(self, cmd, descr):
        if cmd in self.dbg_dialect:
            self.notice_broker("cmd", "\"cmd\":\"%s\"" % self.dbg_dialect[cmd])
            self.notice_anti_flood()
        else:
            rs_log("the \"%s\" command is not available for the current debugger" % cmd)

    # send a go command (Alt-F5) to the debugger (via the broker and dispatcher)
    def go_notice(self):
        self.cmd_notice('go', descr='go')

    # send a go command (Ctrl-Alt-F5) to the debugger (via the broker and dispatcher)
    def run_notice(self):
        self.cmd_notice('run', descr='run')

    # send a single trace command (F11) to the debugger (via the broker and dispatcher)
    def si_notice(self):
        self.cmd_notice('si', descr='trace')

    # send a single step command (F10) to the debugger (via the broker and dispatcher)
    def so_notice(self):
        self.cmd_notice('so', descr='step')

    # send a notice message to the broker process
    def notice_broker(self, type, args=None):
        if not self.broker_sock:
            return

        if args:
            notice = "[notice]{\"type\":\"%s\",%s}\n" % (type, args)
        else:
            notice = "[notice]{\"type\":\"%s\"}\n" % (type)

        try:
            self.broker_sock.sendall(rs_encode(notice))
        except socket.error:
            None

    def stop(self):
        if self.broker_sock:
            self.broker_sock.close()
            self.broker_sock = None

        self.cb_restore_last_line()
        idaapi.refresh_idaview_anyway()
        self.is_active = False
        rs_log("idb is disabled")

    def __init__(self, parser):
        self.color = False
        self.prev_loc = None
        self.prev_node = None
        self.name = idaapi.get_root_filename()
        self.base = idaapi.get_imagebase()
        rs_log("module base 0x%x" % self.base)
        self.base_remote = None
        self.gm = GraphManager()
        self.hexsync = Syncrays()
        self.parser = parser
        self.broker_sock = None
        self.is_active = False
        self.dbg_dialect = None
        self.req_handlers = {
            'broker': self.req_broker,
            'loc': self.req_loc,
            'cmd': self.req_cmd,
            'cmt': self.req_cmt,
            'rcmt': self.req_rcmt,
            'fcmt': self.req_fcmt,
            'raddr': self.req_raddr,
            'rbase': self.req_rbase,
            'cursor': self.req_cursor,
            'patch': self.req_patch,
            'rln': self.req_rln,
            'rrln': self.req_rrln,
            'lbl': self.req_lbl,
            'bc': self.req_bc,
            'bps_get': self.req_bps_get,
            'bps_set': self.req_bps_set,
            'modcheck': self.req_modcheck,
            'dialect': self.req_set_dbg_dialect
        }
        self.prev_req = ""  # used as a cache if json is not completely received


# --------------------------------------------------------------------------


class Broker(QtCore.QProcess):

    QP_STATES = ('Not running', 'Starting', 'Running')
    QP_ERRORS = ('Failed to start', 'Crashed', 'Timedout',
                 'Read error', 'Write Error', 'Unknown Error')

    def cb_on_error(self, error):
        rs_log("[-] broker error: %s" % Broker.QP_ERRORS[error])

    def cb_broker_on_state_change(self, new_state):
        rs_debug("broker new state: %s" % Broker.QP_STATES[new_state])
        if Broker.QP_STATES[new_state] == 'Not running':
            if rsconfig.LOG_TO_FILE_ENABLE:
                rs_log('    check tmp file retsync.<broker|dispatcher>.err if you think this is an error')

    def cb_broker_on_out(self):
        # readAllStandardOutput() returns QByteArray
        data = rs_decode(self.readAllStandardOutput().data())
        batch = data.split('\n')
        for req in batch:
            self.worker.parse_exec(req.strip())

    def __init__(self, parser):
        QtCore.QProcess.__init__(self)

        self.error.connect(self.cb_on_error)
        self.readyReadStandardOutput.connect(self.cb_broker_on_out)
        self.stateChanged.connect(self.cb_broker_on_state_change)

        # create a request handler
        self.worker = RequestHandler(parser)

# --------------------------------------------------------------------------


class DbgDirHlpr(object):

    @staticmethod
    def read_rsds_guid():
        guid = None
        penode = idaapi.netnode()
        penode.create(idautils.peutils_t.PE_NODE)
        rsds = penode.getblob(0, "s")

        if rsds and rsds.startswith(b'RSDS'):
            guid = ("%s" % uuid.UUID(bytes_le=rsds[4:20])).upper()

        return guid

    @staticmethod
    def read_rsds_pdb():
        penode = idaapi.netnode()
        PE_SUPSTR_PDBNM = idautils.peutils_t.PE_ALT_DBG_FPOS - 8
        penode.create(idautils.peutils_t.PE_NODE)
        pdbname = penode.supstr(PE_SUPSTR_PDBNM, 'S')
        return pdbname

    @staticmethod
    def parse_itoldyouso_output(res):
        for line in res.splitlines(True):
            line = line.strip()
            if line.startswith('pdb sig: '):
                return (line.split(':')[-1]).strip()
        return None


# --------------------------------------------------------------------------


class GraphManager():

    def __init__(self):
        self.prev_node = None
        self.graph_viewer = ida_kernwin.get_current_viewer()

    def center(self):
        curnode = ida_graph.viewer_get_curnode(self.graph_viewer)

        if not (self.prev_node == curnode):
            ida_graph.viewer_center_on(self.graph_viewer, curnode)
            self.prev_node = curnode

        return curnode


# --------------------------------------------------------------------------


class CheckBoxActionHandler(idaapi.action_handler_t):
    def __init__(self, cb):
        idaapi.action_handler_t.__init__(self)
        self.cb = cb

    def activate(self, ctx):
        self.cb.toggle()
        return 1

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


# --------------------------------------------------------------------------


class CmdHook(ida_kernwin.UI_Hooks):

    def __init__(self):
        idaapi.UI_Hooks.__init__(self)
        self.hooked = {}
        self.bugfixed = False

        # 74sp1 BUGFIX: IDAPython: ida_kernwin.UI_Hooks.preprocess_action()
        # wouldn't allow inhibiting the action
        pattern = re.compile('preprocess_action\(self, name\) -> int')
        if pattern.search(ida_kernwin.UI_Hooks.preprocess_action.__doc__):
            self.bugfixed = True

    def minver74sp1(self):
        # idaapi.IDA_SDK_VERSION >= 740:
        return self.bugfixed

    def add_hook(self, action_name, callback):
        self.hooked[action_name] = callback

    def del_hook(self, action_name):
        del self.hooked[action_name]

    def preprocess_action(self, action_name):
        if action_name not in self.hooked:
            return 0

        self.hooked[action_name]()
        return 1


# --------------------------------------------------------------------------


class SyncForm_t(PluginForm):

    hotkeys_ctx = []
    cmd_hooks = CmdHook()

    def cb_broker_started(self):
        rs_log("broker started")
        self.btn.setText("Restart")

    def cb_broker_finished(self):
        rs_log("broker finished")
        self.uninit_hotkeys()
        if self.broker:
            self.broker.worker.stop()
            self.cb_sync.stateChanged.disconnect(self.cb_change_state)
            self.cb_sync.toggle()
            self.cb_sync.stateChanged.connect(self.cb_change_state)

        self.btn.setText("Start")

    # send a kill notice to the broker
    # wait at most 2sec for him to gently kill itself
    def smooth_kill(self):
        self.uninit_hotkeys()
        if self.broker:
            broker = self.broker
            self.broker = None
            broker.worker.cb_restore_last_line()
            broker.worker.kill_notice()
            broker.waitForFinished(1500)

    def init_broker(self):
        rs_debug("init_broker")
        modname = self.input.text()
        if modname == "":
            modname = self.handle_name_aliasing()
            self.input.setText(modname)

        cmdline = "\"%s\" -u \"%s\" --idb \"%s\"" % (
                  PYTHON_PATH,
                  BROKER_PATH,
                  modname)
        rs_log("cmdline: %s" % cmdline)

        try:
            self.broker = Broker(self.parser)
            self.broker.started.connect(self.cb_broker_started)
            self.broker.finished.connect(self.cb_broker_finished)
            self.broker.start(cmdline)
        except Exception as e:
            rs_log("[-] failed to start broker: %s\n%s" % (str(e), traceback.format_exc()))
            return

        self.broker.worker.name = modname

    def init_hotkeys(self):
        hotkeys_info = (
            ('F2', self.broker.worker.bp_notice, 'BreakpointToggle'),
            ('F3', self.broker.worker.bp_oneshot_notice),
            ('F10', self.broker.worker.so_notice),
            ('F11', self.broker.worker.si_notice, 'FullScreen'),
            ('Ctrl-F1', self.broker.worker.export_bp_notice, 'ExternalHelp'),
            ('Ctrl-F2', self.broker.worker.hbp_notice),
            ('Ctrl-F3', self.broker.worker.hbp_oneshot_notice),
            ('Alt-F2', self.broker.worker.translate_notice, 'ManualInstruction'),
            ('Alt-F5', self.broker.worker.go_notice),
            ('Ctrl-Alt-F5', self.broker.worker.run_notice),
        )

        if not self.hotkeys_ctx:
            for hk_info in hotkeys_info:
                self.init_single_hotkey(*hk_info)

        # enable ida_kernwin.UI_Hooks
        if self.cmd_hooks.minver74sp1():
            self.cmd_hooks.hook()

    def init_single_hotkey(self, key, fnCb, conflict=None):
        if conflict:
            if self.cmd_hooks.minver74sp1():
                # 'hook' existing action shortcut when possible
                self.cmd_hooks.add_hook(conflict, fnCb)
                return
            else:
                # 'mute' existing action shortcut
                ida_kernwin.update_action_shortcut(conflict, None)

        ctx = idaapi.add_hotkey(key, fnCb)
        if ctx is None:
            rs_log("failed to register hotkey %s" % key)
            del ctx
        else:
            self.hotkeys_ctx.append((ctx, key, conflict))

    def uninit_hotkeys(self):
        # disable ida_kernwin.UI_Hooks
        if self.cmd_hooks.minver74sp1():
            self.cmd_hooks.unhook()

        if not self.hotkeys_ctx:
            return

        # delete registered context and restore original action
        for ctx, key, conflict in self.hotkeys_ctx:
            if idaapi.del_hotkey(ctx):
                del ctx
            else:
                rs_log("failed to delete hotkey %s" % key)

            if conflict and not self.cmd_hooks.minver74sp1():
                ida_kernwin.update_action_shortcut(conflict, key)

        self.hotkeys_ctx = []

    def cb_btn_restart(self):
        rs_log('restarting broker')
        if self.cb_sync.checkState() == QtCore.Qt.Checked:
            self.cb_sync.toggle()
            time.sleep(0.1)
        self.cb_sync.toggle()

    def cb_change_state(self, state):
        if state == QtCore.Qt.Checked:
            rs_log("sync enabled")
            # Restart broker
            self.hotkeys_ctx = []
            self.init_broker()
        else:
            if self.broker:
                self.smooth_kill()
            rs_log("sync disabled\n")

    def cb_hexrays_sync_state(self, state):
        if self.broker:
            if state == QtCore.Qt.Checked:
                rs_log("hexrays sync enabled\n")
                self.broker.worker.hexsync.enable()
            else:
                rs_log("hexrays sync disabled\n")
                self.broker.worker.hexsync.disable()

    def cb_hexrays_toggle(self):
        self.cb_hexrays.toggle()

    # issue a warning if pdb name is different from
    # the name used to register the idb to the dispatcher
    def pdb_name_warning(self, name):
        pdbpath = DbgDirHlpr.read_rsds_pdb()
        if not pdbpath:
            return

        normpath = os.path.normpath(pdbpath.replace("\\", "\\\\"))
        pdb_root, pdb_ext = os.path.splitext(os.path.basename(normpath))
        mod_root, mod_ext = os.path.splitext(name)

        if pdb_root.strip() != mod_root.strip():
            rs_log("hint: pdb name ('%s') differs from registered module name ('%s')" % (pdb_root+mod_ext, name))

    # discover the name used to expose the idb, default is from get_root_filename
    # alias can be defined in '.sync' configuration file
    def handle_name_aliasing(self):
        name = idaapi.get_root_filename()
        rs_log("default idb name: %s" % name)

        try:
            conf = load_configuration(name)
            if conf.path:
                rs_log("found config file: %s" % repr(conf))
            if conf.alias:
                name = conf.alias
                rs_log("overwrite idb name with %s" % name)
        except Exception as e:
            rs_log('failed to load configuration file')

        self.pdb_name_warning(name)
        return name

    def OnCreate(self, form):
        rs_debug("form create")

        # get parent widget
        parent = self.FormToPyQtWidget(form)

        # create global sync checkbox
        self.cb_sync = QtWidgets.QCheckBox('Synchronization enable')
        self.cb_sync.move(20, 20)
        self.cb_sync.stateChanged.connect(self.cb_change_state)

        # create hexrays sync checkbox
        self.cb_hexrays = QtWidgets.QCheckBox('Hex-Rays Synchronization enable')
        self.cb_hexrays.move(20, 20)
        self.cb_hexrays.stateChanged.connect(self.cb_hexrays_sync_state)

        # create label
        label = QtWidgets.QLabel('Overwrite idb name:')
        name = self.handle_name_aliasing()

        # create input field
        self.input = QtWidgets.QLineEdit(parent)
        self.input.setText(name)
        self.input.setMaxLength = 256
        self.input.setFixedWidth(300)

        # create restart button
        self.btn = QtWidgets.QPushButton('restart', parent)
        self.btn.setToolTip('Restart broker.')
        self.btn.clicked.connect(self.cb_btn_restart)

        # create layout
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.cb_sync)
        layout.addWidget(self.cb_hexrays)
        layout.addWidget(label)
        layout.addWidget(self.input)
        layout.addWidget(self.btn, 2, 2)
        layout.setColumnStretch(4, 1)
        layout.setRowStretch(4, 1)
        parent.setLayout(layout)

        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-a', '--address', nargs=1, action='store')
        self.parser.add_argument('msg', nargs=argparse.REMAINDER)

        # synchronization is enabled by default
        self.cb_sync.toggle()

        # register action for hexrays sync
        action_hex_sync_desc = idaapi.action_desc_t(
            'hexrays_sync_toogle:action',
            'Toggle Hex-Rays syncing',
            CheckBoxActionHandler(self.cb_hexrays),
            'Ctrl+H',
            'Toggle Hex-Rays syncing',
            198)

        idaapi.register_action(action_hex_sync_desc)
        idaapi.attach_action_to_toolbar(
            "DebugToolBar",
            'hexrays_sync_toogle:action')

        # register action for global sync
        action_g_sync_desc = idaapi.action_desc_t(
            'g_sync_toogle:action',
            'Toggle syncing',
            CheckBoxActionHandler(self.cb_sync),
            'Ctrl+Shift+S',
            'Toggle syncing',
            203)

        idaapi.register_action(action_g_sync_desc)
        idaapi.attach_action_to_toolbar(
            "DebugToolBar",
            'g_sync_toogle:action')

    def OnClose(self, form):
        rs_debug("form close")
        self.smooth_kill()

        idaapi.unregister_action('hexrays_sync_toogle:action')
        idaapi.detach_action_from_toolbar(
            "DebugToolBar",
            'hexrays_sync_toogle:action')

        idaapi.unregister_action('g_sync_toogle:action')
        idaapi.detach_action_from_toolbar(
            "DebugToolBar",
            'g_sync_toogle:action')

        global SyncForm
        del SyncForm
        SyncForm = None

    def Show(self):
        return PluginForm.Show(self, "ret-sync", options=PluginForm.WOPN_PERSIST)


# --------------------------------------------------------------------------


class RetSyncPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC
    comment = 'Reverse-Engineering Tools synchronization, ret-sync .'
    help = 'Synchronize a debugging session with IDA.'
    wanted_name = 'ret-sync'
    wanted_hotkey = 'Alt-Shift-S'
    global SyncForm
    SyncForm = None

    def init(self):
        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    def run(self, arg):
        if not idaapi.get_root_filename():
            rs_log('please load a file/idb before')
            return

        global SyncForm
        if not SyncForm:
            SyncForm = SyncForm_t()
            SyncForm.Show()
            rs_log("plugin loaded")


def PLUGIN_ENTRY():
    return RetSyncPlugin()


if __name__ == "__main__":
    rs_log("ret-sync is an IDA Pro plugin, please see README for installation notes")


repository: Goatman13/altivec2c
file: altivec2c.py
description: PowerPC Altivec to C plugin for IDA
url: https://github.com/Goatman13/altivec2c/blob/d78aa54129e7025999cdb5d26a90fe1c92f917cc/altivec2c.py
created_at: 2024-02-18 09:43:50+00:00
pushed_at: 2024-08-10 07:11:39+00:00
content:
# altivec To C

from ida_bytes import *
from idaapi import *
from idc import *
import idaapi
import ida_bytes
import idc

def exp2(j):
	i = 0
	r = 1
	while i < j:
		r *= 2
		i += 1
	return "{:.1f}".format(r)

def sign_extend_imm5(_8_16, value):

	if value & 0x10 == 0x10:
		value = (0xFFFFFFF0 | value & 0xF)
	else:
		value &= 0xF
	if _8_16 == 1:
		value &= 0xFF
	elif _8_16 == 2:
		value &= 0xFFFF
	return value

def imm5_to_signed_string(value):

	sign = ""
	imm = value & 0x1F
	if (imm > 0xF):
		imm = ~imm
		imm &= 0xF
		imm += 1
		sign = "-"
	return sign + "0x{:X}".format(imm)

def vaddcuw(vD, vA, vB):

	return "[4x32b] if v{:d} + v{:d} > 0xFFFFFFFF: v{:d} = 1, else v{:d} = 0".format(vA, vB, vD, vD)

def vaddfp(vD, vA, vB):

	return "v{:d}[4xfloat] = v{:d} + v{:d}".format(vD, vA, vB)

def vaddsbs(vD, vA, vB):

	return "v{:d}[16x8b][s] = v{:d} + v{:d}. if abs(v{:d}) > 0x7F: v{:d} = 0x7F | sign".format(vD, vA, vB, vD, vD)

def vaddshs(vD, vA, vB):

	return "v{:d}[8x16b][s] = v{:d} + v{:d}. if abs(v{:d}) > 0x7FFF: v{:d} = 0x7FFF | sign".format(vD, vA, vB, vD, vD)

def vaddsws(vD, vA, vB):

	return "v{:d}[4x32b][s] = v{:d} + v{:d}. if abs(v{:d}) > 0x7FFFFFFF: v{:d} = 0x7FFFFFFF | sign".format(vD, vA, vB, vD, vD)

def vaddubm(vD, vA, vB):

	return "v{:d}[16x8b][u] = v{:d} + v{:d}".format(vD, vA, vB)

def vadduhm(vD, vA, vB):

	return "v{:d}[8x16b][u] = v{:d} + v{:d}".format(vD, vA, vB)

def vadduwm(vD, vA, vB):

	return "v{:d}[4x32b][u] = v{:d} + v{:d}".format(vD, vA, vB)

def vaddubs(vD, vA, vB):

	return "v{:d}[16x8b] = v{:d} + v{:d}. if v{:d} > 0xFF: v{:d} = 0xFF".format(vD, vA, vB, vD, vD)

def vadduhs(vD, vA, vB):

	return "v{:d}[8x16b] = v{:d} + v{:d}. if v{:d} > 0xFFFF: v{:d} = 0xFFFF".format(vD, vA, vB, vD, vD)

def vadduws(vD, vA, vB):

	return "v{:d}[4x32b] = v{:d} + v{:d}. if v{:d} > 0xFFFFFFFF: v{:d} = 0xFFFFFFFF".format(vD, vA, vB, vD, vD)

def vand(vD, vA, vB):

	return "v{:d}[128b] = v{:d} & v{:d}".format(vD, vA, vB)

def vandc(vD, vA, vB):

	return "v{:d}[128b] = v{:d} & ~v{:d}".format(vD, vA, vB)

def vavgsb(vD, vA, vB):

	return "v{:d}[16x8b][s] = (v{:d} + v{:d} + 1) >> 1 (sum before shift is 9 bits value)".format(vD, vA, vB)

def vavgsh(vD, vA, vB):

	return "v{:d}[8x16b][s] = (v{:d} + v{:d} + 1) >> 1 (sum before shift is 17 bits value)".format(vD, vA, vB)

def vavgsw(vD, vA, vB):

	return "v{:d}[4x32b][s] = (v{:d} + v{:d} + 1) >> 1 (sum before shift is 33 bits value)".format(vD, vA, vB)

def vavgub(vD, vA, vB):

	return "v{:d}[16x8b] = (v{:d} + v{:d} + 1) >> 1 (sum before shift is 9 bits value)".format(vD, vA, vB)

def vavguh(vD, vA, vB):

	return "v{:d}[8x16b] = (v{:d} + v{:d} + 1) >> 1 (sum before shift is 17 bits value)".format(vD, vA, vB)

def vavguw(vD, vA, vB):

	return "v{:d}[4x32b] = (v{:d} + v{:d} + 1) >> 1 (sum before shift is 33 bits value)".format(vD, vA, vB)

#todo verify cvts
def vcfsx(vD, imm, vB):

	imm    = exp2(imm)
	return "v{:d}[4xfloat] = (float)(s32)v{:d} / ".format(vD, vB) + imm

def vcfux(vD, imm, vB):

	imm    = exp2(imm)
	return "v{:d}[4xfloat] = (float)(u32)v{:d} / ".format(vD, vB) + imm

def vcmpbfp(vD, vA, vB, vRc):

	cmt    = ".\n[4xfloat] v{:d} = 0\n".format(vD)
	cmt   += "if v{:d} >  v{:d}: v{:d} |= 0x80000000\n".format(vA, vB, vD)
	cmt   += "if v{:d} < -v{:d}: v{:d} |= 0x40000000\n".format(vA, vB, vD)
	if vRc == 1:
		cmt += "affects CR6.eq"
	return cmt

def vcmpeqfp(vD, vA, vB, vRc):

	cmt    = "[4xfloat] if v{:d} == v{:d}: v{:d} = 0xFFFFFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpequb(vD, vA, vB, vRc):

	cmt    = "[16x8b] if v{:d} == v{:d}: v{:d} = 0xFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpequh(vD, vA, vB, vRc):

	cmt    = "[8x16b] if v{:d} == v{:d}: v{:d} = 0xFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpequw(vD, vA, vB, vRc):

	cmt    = "[4x32b] if v{:d} == v{:d}: v{:d} = 0xFFFFFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpgefp(vD, vA, vB, vRc):

	cmt    = "[4xfloat] if v{:d} >= v{:d}: v{:d} = 0xFFFFFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt
	
def vcmpgtfp(vD, vA, vB, vRc):

	cmt    = "[4xfloat] if v{:d} > v{:d}: v{:d} = 0xFFFFFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt
	
def vcmpgtsb(vD, vA, vB, vRc):

	cmt    = "[16x8b][s] if v{:d} > v{:d}: v{:d} = 0xFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpgtsh(vD, vA, vB, vRc):

	cmt    = "[8x16b][s] if v{:d} > v{:d}: v{:d} = 0xFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpgtsw(vD, vA, vB, vRc):

	cmt    = "[4x32b][s] if v{:d} > v{:d}: v{:d} = 0xFFFFFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpgtub(vD, vA, vB, vRc):

	cmt    = "[16x8b] if v{:d} > v{:d}: v{:d} = 0xFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpgtuh(vD, vA, vB, vRc):

	cmt    = "[8x16b] if v{:d} > v{:d}: v{:d} = 0xFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

def vcmpgtuw(vD, vA, vB, vRc):

	cmt    = "[4x32b] if v{:d} > v{:d}: v{:d} = 0xFFFFFFFF, else v{:d} = 0".format(vA, vB, vD, vD)
	if vRc == 1:
		cmt += " (affects CR6)"
	return cmt

#todo verify cvts
def vctsxs(vD, imm, vB):

	imm    = exp2(imm)
	return "v{:d}[4x32b] = (s32)((float)v{:d} * ".format(vD, vB) + imm + ")"

def vctuxs(vD, imm, vB):

	imm    = exp2(imm)
	return "v{:d}[4x32b] = (u32)((float)v{:d} * ".format(vD, vB) + imm + ")"

def vlogefp(vD, vB):

	return "v{:d}[4xfloat] = log2(v{:d})".format(vD, vB)

def vexptefp(vD, vB):

	return "v{:d}[4xfloat] = exp2(v{:d})".format(vD, vB)

def vmaddfp(vD, vA, vB, vC):

	return "v{:d}[4xfloat] = (v{:d} * v{:d}) + v{:d}".format(vD, vA, vC, vB)

def vmaxfp(vD, vA, vB):

	return "[4xfloat] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmaxsb(vD, vA, vB):

	return "[16x8b][s] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmaxsh(vD, vA, vB):

	return "[8x16b][s] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmaxsw(vD, vA, vB):

	return "[4x32b][s] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmaxub(vD, vA, vB):

	return "[16x8b] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmaxuh(vD, vA, vB):

	return "[8x16b] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmaxuw(vD, vA, vB):

	return "[4x32b] if v{:d} >= v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmhaddshs(vD, vA, vB, vC):

	return "v{:d}[8x16b][s] = ((s32)(v{:d} * v{:d}) >> 16) + v{:d} if abs(v{:d}) > 0x7FFF: v{:d} = 0x7FFF | sign".format(vD, vA, vC, vB)

#todo check
def vmhraddshs(vD, vA, vB, vC):

	return "v{:d}[8x16b][s] = (((s32)(v{:d} * v{:d}) + 0x4000) >> 16) + v{:d} if abs(v{:d}) > 0x7FFF: v{:d} = 0x7FFF | sign".format(vD, vA, vC, vB)

def vminfp(vD, vA, vB):

	return "[4xfloat] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vminsb(vD, vA, vB):

	return "[16x8b][s] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vminsh(vD, vA, vB):

	return "[8x16b][s] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vminsw(vD, vA, vB):

	return "[4x32b][s] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vminub(vD, vA, vB):

	return "[16x8b] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vminuh(vD, vA, vB):

	return "[8x16b] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vminuw(vD, vA, vB):

	return "[4x32b] if v{:d} < v{:d}: v{:d} = v{:d}, else v{:d} = v{:d}".format(vA, vB, vD, vA, vD, vB)

def vmladduhm(vD, vA, vB, vC):

	return "v{:d}[8x16b][u] = ((v{:d} * v{:d}) + v{:d}) & 0xFFFF".format(vD, vA, vB, vC)

def vmrghb(vD, vA, vB):

	cmt    = ".\n"
	cmt   += "v{:d}[0].byte  = v{:d}[0].byte\n".format(vD, vA)
	cmt   += "v{:d}[1].byte  = v{:d}[0].byte\n".format(vD, vB)
	cmt   += "v{:d}[2].byte  = v{:d}[1].byte\n".format(vD, vA)
	cmt   += "v{:d}[3].byte  = v{:d}[1].byte\n".format(vD, vB)
	cmt   += "v{:d}[4].byte  = v{:d}[2].byte\n".format(vD, vA)
	cmt   += "v{:d}[5].byte  = v{:d}[2].byte\n".format(vD, vB)
	cmt   += "v{:d}[6].byte  = v{:d}[3].byte\n".format(vD, vA)
	cmt   += "v{:d}[7].byte  = v{:d}[3].byte\n".format(vD, vB)
	cmt   += "v{:d}[8].byte  = v{:d}[4].byte\n".format(vD, vA)
	cmt   += "v{:d}[9].byte  = v{:d}[4].byte\n".format(vD, vB)
	cmt   += "v{:d}[10].byte = v{:d}[5].byte\n".format(vD, vA)
	cmt   += "v{:d}[11].byte = v{:d}[5].byte\n".format(vD, vB)
	cmt   += "v{:d}[12].byte = v{:d}[6].byte\n".format(vD, vA)
	cmt   += "v{:d}[13].byte = v{:d}[6].byte\n".format(vD, vB)
	cmt   += "v{:d}[14].byte = v{:d}[7].byte\n".format(vD, vA)
	cmt   += "v{:d}[15].byte = v{:d}[7].byte".format(vD, vB)
	return cmt

def vmrghh(vD, vA, vB):

	cmt    = ".\n"
	cmt   += "v{:d}[0].half = v{:d}[0].half\n".format(vD, vA)
	cmt   += "v{:d}[1].half = v{:d}[0].half\n".format(vD, vB)
	cmt   += "v{:d}[2].half = v{:d}[1].half\n".format(vD, vA)
	cmt   += "v{:d}[3].half = v{:d}[1].half\n".format(vD, vB)
	cmt   += "v{:d}[4].half = v{:d}[2].half\n".format(vD, vA)
	cmt   += "v{:d}[5].half = v{:d}[2].half\n".format(vD, vB)
	cmt   += "v{:d}[6].half = v{:d}[3].half\n".format(vD, vA)
	cmt   += "v{:d}[7].half = v{:d}[3].half".format(vD, vB)
	return cmt

def vmrghw(vD, vA, vB):

	cmt    = ".\n"
	cmt   += "v{:d}[0].word = v{:d}[0].word\n".format(vD, vA)
	cmt   += "v{:d}[1].word = v{:d}[0].word\n".format(vD, vB)
	cmt   += "v{:d}[2].word = v{:d}[1].word\n".format(vD, vA)
	cmt   += "v{:d}[3].word = v{:d}[1].word".format(vD, vB)
	return cmt

def vmrglb(vD, vA, vB):

	cmt    = ".\n"
	cmt   += "v{:d}[0].byte  = v{:d}[8].byte\n".format(vD, vA)
	cmt   += "v{:d}[1].byte  = v{:d}[8].byte\n".format(vD, vB)
	cmt   += "v{:d}[2].byte  = v{:d}[9].byte\n".format(vD, vA)
	cmt   += "v{:d}[3].byte  = v{:d}[9].byte\n".format(vD, vB)
	cmt   += "v{:d}[4].byte  = v{:d}[10].byte\n".format(vD, vA)
	cmt   += "v{:d}[5].byte  = v{:d}[10].byte\n".format(vD, vB)
	cmt   += "v{:d}[6].byte  = v{:d}[11].byte\n".format(vD, vA)
	cmt   += "v{:d}[7].byte  = v{:d}[11].byte\n".format(vD, vB)
	cmt   += "v{:d}[8].byte  = v{:d}[12].byte\n".format(vD, vA)
	cmt   += "v{:d}[9].byte  = v{:d}[12].byte\n".format(vD, vB)
	cmt   += "v{:d}[10].byte = v{:d}[13].byte\n".format(vD, vA)
	cmt   += "v{:d}[11].byte = v{:d}[13].byte\n".format(vD, vB)
	cmt   += "v{:d}[12].byte = v{:d}[14].byte\n".format(vD, vA)
	cmt   += "v{:d}[13].byte = v{:d}[14].byte\n".format(vD, vB)
	cmt   += "v{:d}[14].byte = v{:d}[15].byte\n".format(vD, vA)
	cmt   += "v{:d}[15].byte = v{:d}[15].byte".format(vD, vB)
	return cmt

def vmrglh(vD, vA, vB):

	cmt    = ".\n"
	cmt   += "v{:d}[0].half = v{:d}[4].half\n".format(vD, vA)
	cmt   += "v{:d}[1].half = v{:d}[4].half\n".format(vD, vB)
	cmt   += "v{:d}[2].half = v{:d}[5].half\n".format(vD, vA)
	cmt   += "v{:d}[3].half = v{:d}[5].half\n".format(vD, vB)
	cmt   += "v{:d}[4].half = v{:d}[6].half\n".format(vD, vA)
	cmt   += "v{:d}[5].half = v{:d}[6].half\n".format(vD, vB)
	cmt   += "v{:d}[6].half = v{:d}[7].half\n".format(vD, vA)
	cmt   += "v{:d}[7].half = v{:d}[7].half".format(vD, vB)
	return cmt

def vmrglw(vD, vA, vB):

	cmt    = ".\n"
	cmt   += "v{:d}[0].word = v{:d}[2].word\n".format(vD, vA)
	cmt   += "v{:d}[1].word = v{:d}[2].word\n".format(vD, vB)
	cmt   += "v{:d}[2].word = v{:d}[3].word\n".format(vD, vA)
	cmt   += "v{:d}[3].word = v{:d}[3].word".format(vD, vB)
	return cmt

# vmsummbm todo...

def vmulfp(vD, vA, vB):

	return "v{:d}[4xfloat] = v{:d} * v{:d}".format(vD, vA, vB)


def vmulesb(vD, vA, vB):

	cmt    = ".\nsigned\n"
	cmt  += "v{:d}[0].half = v{:d}[0].byte * v{:d}[0].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].half = v{:d}[2].byte * v{:d}[2].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].half = v{:d}[4].byte * v{:d}[4].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].half = v{:d}[6].byte * v{:d}[6].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[4].half = v{:d}[8].byte * v{:d}[8].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[5].half = v{:d}[10].byte * v{:d}[10].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[6].half = v{:d}[12].byte * v{:d}[12].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[7].half = v{:d}[14].byte * v{:d}[14].byte".format(vD, vA, vB)
	return cmt

def vmulesh(vD, vA, vB):

	cmt    = ".\nsigned\n"
	cmt  += "v{:d}[0].word = v{:d}[0].half * v{:d}[0].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].word = v{:d}[2].half * v{:d}[2].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].word = v{:d}[4].half * v{:d}[4].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].word = v{:d}[6].half * v{:d}[6].half".format(vD, vA, vB)
	return cmt

def vmuleub(vD, vA, vB):

	cmt    = ".\n"
	cmt  += "v{:d}[0].half = v{:d}[0].byte * v{:d}[0].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].half = v{:d}[2].byte * v{:d}[2].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].half = v{:d}[4].byte * v{:d}[4].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].half = v{:d}[6].byte * v{:d}[6].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[4].half = v{:d}[8].byte * v{:d}[8].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[5].half = v{:d}[10].byte * v{:d}[10].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[6].half = v{:d}[12].byte * v{:d}[12].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[7].half = v{:d}[14].byte * v{:d}[14].byte".format(vD, vA, vB)
	return cmt

def vmuleuh(vD, vA, vB):

	cmt    = ".\n"
	cmt  += "v{:d}[0].word = v{:d}[0].half * v{:d}[0].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].word = v{:d}[2].half * v{:d}[2].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].word = v{:d}[4].half * v{:d}[4].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].word = v{:d}[6].half * v{:d}[6].half".format(vD, vA, vB)
	return cmt

def vmulosb(vD, vA, vB):

	cmt    = ".\nsigned\n"
	cmt  += "v{:d}[0].half = v{:d}[1].byte * v{:d}[1].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].half = v{:d}[3].byte * v{:d}[3].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].half = v{:d}[5].byte * v{:d}[5].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].half = v{:d}[7].byte * v{:d}[7].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[4].half = v{:d}[9].byte * v{:d}[9].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[5].half = v{:d}[11].byte * v{:d}[11].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[6].half = v{:d}[13].byte * v{:d}[13].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[7].half = v{:d}[15].byte * v{:d}[15].byte".format(vD, vA, vB)
	return cmt

def vmulosh(vD, vA, vB):

	cmt    = ".\nsigned\n"
	cmt  += "v{:d}[0].word = v{:d}[1].half * v{:d}[1].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].word = v{:d}[3].half * v{:d}[3].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].word = v{:d}[5].half * v{:d}[5].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].word = v{:d}[7].half * v{:d}[7].half".format(vD, vA, vB)
	return cmt

def vmuloub(vD, vA, vB):

	cmt    = ".\n"
	cmt  += "v{:d}[0].half = v{:d}[1].byte * v{:d}[1].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].half = v{:d}[3].byte * v{:d}[3].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].half = v{:d}[5].byte * v{:d}[5].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].half = v{:d}[7].byte * v{:d}[7].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[4].half = v{:d}[9].byte * v{:d}[9].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[5].half = v{:d}[11].byte * v{:d}[11].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[6].half = v{:d}[13].byte * v{:d}[13].byte\n".format(vD, vA, vB)
	cmt  += "v{:d}[7].half = v{:d}[15].byte * v{:d}[15].byte".format(vD, vA, vB)
	return cmt

def vmulouh(vD, vA, vB):

	cmt    = ".\n"
	cmt  += "v{:d}[0].word = v{:d}[1].half * v{:d}[1].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[1].word = v{:d}[3].half * v{:d}[3].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[2].word = v{:d}[5].half * v{:d}[5].half\n".format(vD, vA, vB)
	cmt  += "v{:d}[3].word = v{:d}[7].half * v{:d}[7].half".format(vD, vA, vB)
	return cmt

def vnmsubfp(vD, vA, vB, vC):

	return "v{:d}[4xfloat] = (v{:d} * v{:d}) - v{:d}".format(vD, vA, vC, vB)

def vnor(vD, vA, vB):

	return "v{:d}[4x32b] = ~(v{:d} | v{:d})".format(vD, vA, vB)

def vnot(vD, vA):

	return "v{:d}[4x32b] = ~v{:d}".format(vD, vA)

def vor(vD, vA, vB):

	return "v{:d}[4x32b] = v{:d} | v{:d}".format(vD, vA, vB)

def vmr(vD, vA):

	return "v{:d}[4x32b] = v{:d}".format(vD, vA)

def vperm(vD, vA, vB, vC):

	return ".\nfor (field = 0; field <= 15; field++)\n{{\n  x = v{:d}.byte[field]\n  if      (x & 0x10) == 0x00) {{v{:d}.byte[field] = v{:d}.byte[x & 0x0f];}}\n  else if (x & 0x10) == 0x10) {{v{:d}.byte[field] = v{:d}.byte[x & 0x0f];}}\n}}".format(vC, vD, vA, vD, vB)

def vpkpx(vD, vA, vB):

	cmt   = "temp  = (v{:d}[0-3].word >> 3) & 0x1F\n".format(vA)
	cmt  += "temp |= (v{:d}[0-3].word >> 6) & 0x3E0\n".format(vA)
	cmt  += "temp |= (v{:d}[0-3].word >> 9) & 0xFC00\n".format(vA)
	cmt  += "v{:d}[4x16b][0-3] = temp\n".format(vD)
	cmt  += "temp  = (v{:d}[0-3].word >> 3) & 0x1F\n".format(vB)
	cmt  += "temp |= (v{:d}[0-3].word >> 6) & 0x3E0\n".format(vB)
	cmt  += "temp |= (v{:d}[0-3].word >> 9) & 0xFC00\n".format(vB)
	cmt  += "v{:d}[4x16b][4-7] = temp".format(vD)
	return cmt

# todo pack opcodes

def vrefp(vD, vB):

	return "v{:d}[4xfloat] = 1.0 / v{:d}".format(vD, vB)

def vrfim(vD, vB):

	return "v{:d}[4xfloat] = RoundTowardNegativeInf(v{:d})".format(vD, vB)

def vrfin(vD, vB):

	return "v{:d}[4xfloat] = RoundTowardNearest(v{:d})".format(vD, vB)

def vrfip(vD, vB):

	return "v{:d}[4xfloat] = RoundTowardPositiveInf(v{:d})".format(vD, vB)

def vrfiz(vD, vB):

	return "v{:d}[4xfloat] = RoundTowardZero(v{:d})".format(vD, vB)

def vrlb(vD, vA, vB):

	return "v{:d}[16x8b] = rol8(v{:d}, (v{:d} & 7))".format(vD, vA, vB)

def vrlh(vD, vA, vB):

	return "v{:d}[8x16b] = rol16(v{:d}, (v{:d} & 0xF))".format(vD, vA, vB)

def vrlw(vD, vA, vB):

	return "v{:d}[4x32b] = rol32(v{:d}, (v{:d} & 0x1F))".format(vD, vA, vB)

def vrsqrtefp(vD, vB):

	return "v{:d}[4xfloat] = 1.0 / (v{:d} * v{:d})".format(vD, vB, vB)

def vsel(vD, vA, vB, vC):

	return "[128b] if bit in v{:d} == 0 take bit from v{:d}, else take bit from v{:d}".format(vC, vA, vB)

def vsl(vD, vA, vB):

	return "v{:d}[128b] = v{:d} << (v{:d} & 7)".format(vD, vA, vB)

def vslb(vD, vA, vB):

	return "v{:d}[16x8b] = v{:d} << (v{:d} & 7)".format(vD, vA, vB)

def vsldoi(vD, vA, vB, sh):

	sh <<= 3
	shr  = 128 - sh
	return "v{:d}[128b] = (v{:d} << {:d}) | (v{:d} >> {:d})".format(vD, vA, sh, vB, shr)

def vslh(vD, vA, vB):

	return "v{:d}[8x16b] = v{:d} << (v{:d} & 0xF)".format(vD, vA, vB)

def vslo(vD, vA, vB):

	return "v{:d}[128b] = v{:d} << (v{:d} & 0x78)".format(vD, vA, vB)

def vslw(vD, vA, vB):

	return "v{:d}[4x32b] = v{:d} << (v{:d} & 0x1F)".format(vD, vA, vB)

def vspltb(vD, imm, vB):

	return "v{:d}[16x8b] = v{:d}[{:d}].byte".format(vD, vB, imm)

def vsplth(vD, imm, vB):
	
	imm &= 7
	return "v{:d}[8x16b] = v{:d}[{:d}].half".format(vD, vB, imm)

def vspltisb(vD, simm):

	neg = ""
	if simm > 0xF:
		neg = " ("
		neg += imm5_to_signed_string(simm)
		neg += ")"
	simm = sign_extend_imm5(1, simm)
	return "v{:d}[16x8b] = 0x{:02X}".format(vD, simm) + neg

def vspltish(vD, simm):

	neg = ""
	if simm > 0xF:
		neg = " ("
		neg += imm5_to_signed_string(simm)
		neg += ")"
	simm = sign_extend_imm5(2, simm)
	return "v{:d}[8x16b] = 0x{:04X}".format(vD, simm) + neg

def vspltisw(vD, simm):

	neg = ""
	if simm > 0xF:
		neg = " ("
		neg += imm5_to_signed_string(simm)
		neg += ")"
	simm = sign_extend_imm5(0, simm)
	return "v{:d}[4x32b] = 0x{:08X}".format(vD, simm) + neg

def vspltw(vD, imm, vB):
	
	imm &= 3
	return "v{:d}[4x32b] = v{:d}[{:d}].word".format(vD, vB, imm)

def vsr(vD, vA, vB):

	return "v{:d}[128b] = v{:d} >> (v{:d} & 7)".format(vD, vA, vB)

def vsrab(vD, vA, vB):

	return "v{:d}[16x8b][arithm] = v{:d} >> (v{:d} & 7)".format(vD, vA, vB)

def vsrah(vD, vA, vB):

	return "v{:d}[8x16b][arithm]= v{:d} >> (v{:d} & 0xF)".format(vD, vA, vB)

def vsraw(vD, vA, vB):

	return "v{:d}[4x32b][arithm] = v{:d} >> (v{:d} & 0x1F)".format(vD, vA, vB)

def vsrb(vD, vA, vB):

	return "v{:d}[16x8b] = v{:d} >> (v{:d} & 7)".format(vD, vA, vB)

def vsrh(vD, vA, vB):

	return "v{:d}[8x16b] = v{:d} >> (v{:d} & 0xF)".format(vD, vA, vB)

def vsro(vD, vA, vB):

	return "v{:d}[128b] = v{:d} >> (v{:d} & 0x78)".format(vD, vA, vB)

def vsrw(vD, vA, vB):

	return "v{:d}[4x32b] = v{:d} >> (v{:d} & 0x1F)".format(vD, vA, vB)

# todo vsubcuw

def vsubfp(vD, vA, vB):

	return "v{:d}[4xfloat] = v{:d} - v{:d}".format(vD, vA, vB)


#VMX128
def vmaddfp128(vD, vA, vB):

	return "v{:d}[4xfloat] = (v{:d} * v{:d}) + v{:d}".format(vD, vA, vB, vD)

def vmaddcfp128(vD, vA, vB):

	return "v{:d}[4xfloat] = (v{:d} * v{:d}) + v{:d}".format(vD, vA, vD, vB)

def vnmsubfp128(vD, vA, vB):

	return "v{:d}[4xfloat] = (v{:d} * v{:d}) - v{:d}".format(vD, vA, vD, vB)

def vpermwi128(vD, vB, vPerm):

	z  = 0xAAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD
	sa = ((vPerm >> 6) & 3) * 32
	sb = ((vPerm >> 4) & 3) * 32
	sc = ((vPerm >> 2) & 3) * 32
	sd = ((vPerm >> 0) & 3) * 32
	a  = (z >> (96-sa)) & 0xFFFFFFFF
	b  = (z >> (96-sb)) & 0xFFFFFFFF
	c  = (z >> (96-sc)) & 0xFFFFFFFF
	d  = (z >> (96-sd)) & 0xFFFFFFFF
	return "v{:d}[128b] = v{:d}: {:08X}:{:08X}:{:08X}:{:08X}".format(vD,vB,a,b,c,d)

def vrlimi128(vD, vB, Imm ,Rot):

	# rotate
	z  = 0x0123
	z  = (z << (Rot * 4)) | (z << (16 - (Rot * 4)))
	za = (z >> 12) & 3
	zb = (z >>  8) & 3
	zc = (z >>  4) & 3
	zd = (z >>  0) & 3
	# mask
	a = (Imm >> 3) & 1
	b = (Imm >> 2) & 1
	c = (Imm >> 1) & 1
	d = (Imm >> 0) & 1
	# result
	result = ".\n"
	if a == 1:
		result += "v{:d}[0].word = v{:d}[{:d}].word\n".format(vD,vB,za)
	else:
		result += "v{:d}[0].word = v{:d}[0].word\n".format(vD,vD)
	if b == 1:
		result += "v{:d}[1].word = v{:d}[{:d}].word\n".format(vD,vB,zb)
	else:
		result += "v{:d}[1].word = v{:d}[1].word\n".format(vD,vD)
	if c == 1:
		result += "v{:d}[2].word = v{:d}[{:d}].word\n".format(vD,vB,zc)
	else:
		result += "v{:d}[2].word = v{:d}[2].word\n".format(vD,vD)
	if d == 1:
		result += "v{:d}[3].word = v{:d}[{:d}].word".format(vD,vB,zd)
	else:
		result += "v{:d}[3].word = v{:d}[3].word".format(vD,vD)
	return result



def altivecAsm2C(addr):

	opcode = get_wide_dword(addr)
	opcode_name = print_insn_mnem(addr)
	
	#Altivec
	vA     = (opcode >> 16) & 0x1F
	vB     = (opcode >> 11) & 0x1F
	vC     = (opcode >> 6 ) & 0x1F
	vD     = (opcode >> 21) & 0x1F
	vS     = (opcode >> 21) & 0x1F
	imm    = (opcode >> 16) & 0x1F
	simm   = (opcode >> 16) & 0x1F
	sh     = (opcode >> 6 ) & 0xF
	vRc    = (opcode >> 10) & 1

	#VMX128
	vmxA    = (opcode >> 16) & 0x1F | opcode & 0x20 | (opcode >> 4) & 0x40
	vmxB    = (opcode >> 11) & 0x1F | (opcode << 5) & 0x60
	vmxC    = (opcode >> 6)  & 0x7
	vmxD    = (opcode >> 21) & 0x1F | (opcode << 3) & 0x60
	vmxImm  = (opcode >> 16) & 0x1F
	vmxSimm = (opcode >> 16) & 0x1F
	vmxPerm = (opcode >> 16) & 0x1F | (opcode >> 1) & 0xE0
	vmxRc   = (opcode >> 6)  & 0x1
	vmxRot  = (opcode >> 6)  & 0x3
	vmxShb  = (opcode >> 6)  & 0xF

	#VSX / VSX-2
	#vsxA    = (opcode >> 16) & 0x1F | (opcode << 3) & 0x20
	#vsxB    = (opcode >> 11) & 0x1F | (opcode << 4) & 0x20
	#vsxC    = (opcode >> 6)  & 0x1F | (opcode << 2) & 0x20
	#vsxD    = (opcode >> 21) & 0x1F | (opcode << 5) & 0x20
	#vsxImm  = (opcode >> 16) & 0x3
	#vsxBf   = (opcode >> 23) & 0x7
	#vsxRc   = (opcode >> 10) & 0x1
	#vsxDm  =  (opcode >> 7)  & 0x3
	#vsxShw  = (opcode >> 7)  & 0x3

	if   opcode_name == "vaddcuw":       return vaddcuw(vD, vA, vB)
	elif opcode_name == "vaddfp":        return vaddfp(vD, vA, vB)
	elif opcode_name == "vaddsbs":       return vaddsbs(vD, vA, vB)
	elif opcode_name == "vaddshs":       return vaddshs(vD, vA, vB)
	elif opcode_name == "vaddsws":       return vaddsws(vD, vA, vB)
	elif opcode_name == "vaddubm":       return vaddubm(vD, vA, vB)
	elif opcode_name == "vadduhm":       return vadduhm(vD, vA, vB)
	elif opcode_name == "vadduwm":       return vadduwm(vD, vA, vB)
	elif opcode_name == "vaddubs":       return vaddubs(vD, vA, vB)
	elif opcode_name == "vadduhs":       return vadduhs(vD, vA, vB)
	elif opcode_name == "vadduws":       return vadduws(vD, vA, vB)
	elif opcode_name == "vand":          return vand(vD, vA, vB)
	elif opcode_name == "vandc":         return vandc(vD, vA, vB)
	elif opcode_name == "vavgsb":        return vavgsb(vD, vA, vB)
	elif opcode_name == "vavgsh":        return vavgsh(vD, vA, vB)
	elif opcode_name == "vavgsw":        return vavgsw(vD, vA, vB)
	elif opcode_name == "vavgub":        return vavgub(vD, vA, vB)
	elif opcode_name == "vavguh":        return vavguh(vD, vA, vB)
	elif opcode_name == "vavguw":        return vavguw(vD, vA, vB)
	elif opcode_name == "vcfsx":         return vcfsx(vD, imm, vB)
	elif opcode_name == "vcfux":         return vcfux(vD, imm, vB)
	elif opcode_name == "vcmpbfp":       return vcmpbfp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpeqfp":      return vcmpeqfp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpequb":      return vcmpequb(vD, vA, vB, vRc)
	elif opcode_name == "vcmpequh":      return vcmpequh(vD, vA, vB, vRc)
	elif opcode_name == "vcmpequw":      return vcmpequw(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgefp":      return vcmpgefp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtfp":      return vcmpgtfp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtsb":      return vcmpgtsb(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtsh":      return vcmpgtsh(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtsw":      return vcmpgtsw(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtub":      return vcmpgtub(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtuh":      return vcmpgtuh(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtuw":      return vcmpgtuw(vD, vA, vB, vRc)
	elif opcode_name == "vcmpbfp.":      return vcmpbfp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpeqfp.":     return vcmpeqfp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpequb.":     return vcmpequb(vD, vA, vB, vRc)
	elif opcode_name == "vcmpequh.":     return vcmpequh(vD, vA, vB, vRc)
	elif opcode_name == "vcmpequw.":     return vcmpequw(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgefp.":     return vcmpgefp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtfp.":     return vcmpgtfp(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtsb.":     return vcmpgtsb(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtsh.":     return vcmpgtsh(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtsw.":     return vcmpgtsw(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtub.":     return vcmpgtub(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtuh.":     return vcmpgtuh(vD, vA, vB, vRc)
	elif opcode_name == "vcmpgtuw.":     return vcmpgtuw(vD, vA, vB, vRc)
	elif opcode_name == "vctsxs":        return vctsxs(vD, imm, vB)
	elif opcode_name == "vctuxs":        return vctuxs(vD, imm, vB)
	elif opcode_name == "vlogefp":       return vlogefp(vD, vB)
	elif opcode_name == "vexptefp":      return vexptefp(vD, vB)
	elif opcode_name == "vmaddfp":       return vmaddfp(vD, vA, vB, vC)
	elif opcode_name == "vmaxfp":        return vmaxfp(vD, vA, vB)
	elif opcode_name == "vmaxsb":        return vmaxsb(vD, vA, vB)
	elif opcode_name == "vmaxsh":        return vmaxsh(vD, vA, vB)
	elif opcode_name == "vmaxsw":        return vmaxsw(vD, vA, vB)
	elif opcode_name == "vmaxub":        return vmaxub(vD, vA, vB)
	elif opcode_name == "vmaxuh":        return vmaxuh(vD, vA, vB)
	elif opcode_name == "vmaxuw":        return vmaxuw(vD, vA, vB)
	elif opcode_name == "vmhaddshs":     return vmhaddshs(vD, vA, vB, vC)
	elif opcode_name == "vmhraddshs":    return vmhraddshs(vD, vA, vB, vC)
	elif opcode_name == "vminfp":        return vminfp(vD, vA, vB)
	elif opcode_name == "vminsb":        return vminsb(vD, vA, vB)
	elif opcode_name == "vminsh":        return vminsh(vD, vA, vB)
	elif opcode_name == "vminsw":        return vminsw(vD, vA, vB)
	elif opcode_name == "vminub":        return vminub(vD, vA, vB)
	elif opcode_name == "vminuh":        return vminuh(vD, vA, vB)
	elif opcode_name == "vminuw":        return vminuw(vD, vA, vB)
	elif opcode_name == "vmladduhm":     return vmladduhm(vD, vA, vB, vC)
	elif opcode_name == "vmrghb":        return vmrghb(vD, vA, vB)
	elif opcode_name == "vmrghh":        return vmrghh(vD, vA, vB)
	elif opcode_name == "vmrghw":        return vmrghw(vD, vA, vB)
	elif opcode_name == "vmrglb":        return vmrglb(vD, vA, vB)
	elif opcode_name == "vmrglh":        return vmrglh(vD, vA, vB)
	elif opcode_name == "vmrglw":        return vmrglw(vD, vA, vB)
	elif opcode_name == "vmulfp":        return vmulfp(vD, vA, vB)
	elif opcode_name == "vmulesb":       return vmulesb(vD, vA, vB)
	elif opcode_name == "vmulesh":       return vmulesh(vD, vA, vB)
	elif opcode_name == "vmuleub":       return vmuleub(vD, vA, vB)
	elif opcode_name == "vmuleuh":       return vmuleuh(vD, vA, vB)
	elif opcode_name == "vmulosb":       return vmulosb(vD, vA, vB)
	elif opcode_name == "vmulosh":       return vmulosh(vD, vA, vB)
	elif opcode_name == "vmuloub":       return vmuloub(vD, vA, vB)
	elif opcode_name == "vmulouh":       return vmulouh(vD, vA, vB)
	elif opcode_name == "vnmsubfp":      return vnmsubfp(vD, vA, vB, vC)
	elif opcode_name == "vnor":          return vnor(vD, vA, vB)
	elif opcode_name == "vnot":          return vnot(vD, vA)
	elif opcode_name == "vor":           return vor(vD, vA, vB)
	elif opcode_name == "vmr":           return vmr(vD, vA)
	elif opcode_name == "vperm":         return vperm(vD, vA, vB, vC)	
	elif opcode_name == "vpkpx":         return vpkpx(vD, vA, vB)
	elif opcode_name == "vrefp":         return vrefp(vD, vB)
	elif opcode_name == "vrfim":         return vrfim(vD, vB)
	elif opcode_name == "vrfin":         return vrfin(vD, vB)
	elif opcode_name == "vrfip":         return vrfip(vD, vB)
	elif opcode_name == "vrfiz":         return vrfiz(vD, vB)
	elif opcode_name == "vrlb":          return vrlb(vD, vA, vB)
	elif opcode_name == "vrlh":          return vrlh(vD, vA, vB)
	elif opcode_name == "vrlw":          return vrlw(vD, vA, vB)
	elif opcode_name == "vrsqrtefp":     return vrsqrtefp(vD, vB)
	elif opcode_name == "vsel":          return vsel(vD, vA, vB, vC)
	elif opcode_name == "vsl":           return vsl(vD, vA, vB)
	elif opcode_name == "vslb":          return vslb(vD, vA, vB)
	elif opcode_name == "vsldoi":        return vsldoi(vD, vA, vB, sh)
	elif opcode_name == "vslh":          return vslh(vD, vA, vB)
	elif opcode_name == "vslo":          return vslo(vD, vA, vB)
	elif opcode_name == "vslw":          return vslw(vD, vA, vB)
	elif opcode_name == "vspltb":        return vspltb(vD, imm, vB)
	elif opcode_name == "vsplth":        return vsplth(vD, imm, vB)
	elif opcode_name == "vspltisb":      return vspltisb(vD, simm)
	elif opcode_name == "vspltish":      return vspltish(vD, simm)
	elif opcode_name == "vspltisw":      return vspltisw(vD, simm)
	elif opcode_name == "vspltw":        return vspltw(vD, imm, vB)
	elif opcode_name == "vsr":           return vsr(vD, vA, vB)
	elif opcode_name == "vsrab":         return vsrab(vD, vA, vB)
	elif opcode_name == "vsrah":         return vsrah(vD, vA, vB)
	elif opcode_name == "vsraw":         return vsraw(vD, vA, vB)
	elif opcode_name == "vsrb":          return vsrb(vD, vA, vB)
	elif opcode_name == "vsrh":          return vsrh(vD, vA, vB)
	elif opcode_name == "vsro":          return vsro(vD, vA, vB)
	elif opcode_name == "vsrw":          return vsrw(vD, vA, vB)	
	elif opcode_name == "vsubfp":        return vsubfp(vD, vA, vB)	
	#VMX128
	elif opcode_name == "vaddfp128":     return vaddfp(vmxD, vmxA, vmxB)
	elif opcode_name == "vand128":       return vand(vmxD, vmxA, vmxB)
	elif opcode_name == "vandc128":      return vandc(vmxD, vmxA, vmxB)
	elif opcode_name == "vcmpeqfp128":   return vcmpeqfp(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpeqfp128.":  return vcmpeqfp(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpequw128":   return vcmpequw(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpequw128.":  return vcmpequw(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpgefp128":   return vcmpgefp(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpgefp128.":  return vcmpgefp(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpgtfp128":   return vcmpgtfp(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcmpgtfp128.":  return vcmpgtfp(vmxD, vmxA, vmxB, vmxRc)
	elif opcode_name == "vcsxwfp128":    return vcfsx(vmxD, vmxImm, vmxB) # correct?
	elif opcode_name == "vcuxwfp128":    return vcfux(vmxD, vmxImm, vmxB) # correct?
	elif opcode_name == "vlogefp128":    return vlogefp(vmxD, vmxB)
	elif opcode_name == "vexptefp128":   return vexptefp(vmxD, vmxB)
	elif opcode_name == "vmaddcfp128":   return vmaddfp128(vmxD, vmxA, vmxB)
	elif opcode_name == "vmaddfp128":    return vmaddcfp128(vmxD, vmxA, vmxB)
	elif opcode_name == "vmaxfp128":     return vmaxfp(vmxD, vmxA, vmxB)
	elif opcode_name == "vminfp128":     return vminfp(vmxD, vmxA, vmxB)
	elif opcode_name == "vmrghw128":     return vmrghw(vmxD, vmxA, vmxB)
	elif opcode_name == "vmrglw128":     return vmrglw(vmxD, vmxA, vmxB)
	elif opcode_name == "vmulfp128":     return vmulfp(vmxD, vmxA, vmxB)
	elif opcode_name == "vnmsubfp128":   return vnmsubfp128(vmxD, vmxA, vmxB)
	elif opcode_name == "vnor128":       return vnor(vmxD, vmxA, vmxB)
	elif opcode_name == "vnot128":       return vnot(vmxD, vmxA)
	elif opcode_name == "vor128":        return vor(vmxD, vmxA, vmxB)
	elif opcode_name == "vmr128":        return vmr(vmxD, vmxA)
	elif opcode_name == "vperm128":      return vperm(vmxD, vmxA, vmxB, vmxC)
	elif opcode_name == "vpermwi128":    return vpermwi128(vmxD, vmxB, vmxPerm)
	elif opcode_name == "vrefp128":      return vrefp(vmxD, vmxB)
	elif opcode_name == "vrfim128":      return vrfim(vmxD, vmxB)
	elif opcode_name == "vrfin128":      return vrfin(vmxD, vmxB)
	elif opcode_name == "vrfip128":      return vrfip(vmxD, vmxB)
	elif opcode_name == "vrfiz128":      return vrfiz(vmxD, vmxB)
	elif opcode_name == "vrlw128":       return vrlw(vmxD, vmxA, vmxB)
	elif opcode_name == "vrlimi128":     return vrlimi128(vmxD, vmxImm, vmxB ,vmxRot)
	elif opcode_name == "vrsqrtefp128":  return vrsqrtefp(vmxD, vmxB)
	elif opcode_name == "vsldoi128":     return vsldoi(vmxD, vmxA, vmxB, vmxShb)
	elif opcode_name == "vslw128":       return vslw(vmxD, vmxA, vmxB)
	elif opcode_name == "vspltisw128":   return vspltisw(vmxD, vmxSimm)
	elif opcode_name == "vspltw128":     return vspltw(vmxD, vmxImm, vmxB)
	elif opcode_name == "vsubfp128":     return vsubfp(vmxD, vmxA, vmxB)	

	#VSX / VSX-2
	#elif opcode_name == "xsabsdp":       return xsabsdp(vsxD, vsxB)

	return 0

def run_task(start_addr, end_addr, always_insert_comment):

	# convert all instructions within the bounds
	addr = start_addr
	while(addr < end_addr):
		print_str = altivecAsm2C(addr)
		if(print_str != 0 and print_str != 1):
			set_cmt(addr, print_str, False)
		elif (print_str == 0 and always_insert_comment == True):
			msg("0x{:X}: Error converting altivec to C code\n".format(addr))
		addr += 4

def PluginMain():

	# select current line or selected lines
	always_insert_comment = False
	start_addr = read_selection_start()
	end_addr = read_selection_end()
	if(start_addr == BADADDR):
		start_addr = get_screen_ea()
		end_addr = start_addr + 4
		always_insert_comment = True

	run_task(start_addr, end_addr, always_insert_comment)


def PluginMainF():

	# convert current function
	p_func = get_func(get_screen_ea())
	if(type(p_func) != type(None)):
		start_addr = p_func.start_ea
		end_addr = p_func.end_ea
		always_insert_comment = False
		run_task(start_addr, end_addr, always_insert_comment)
	else:
		msg("Not in a function, so can't do altivec to C conversion for the current function!\n")


#/***************************************************************************************************
#*
#*	Strings required for IDA Pro's PLUGIN descriptor block
#*
#***************************************************************************************************/
#
G_PLUGIN_COMMENT = "Altivec To C Conversion Assist"
G_PLUGIN_HELP = "This plugin assists in converting altivec instructions into their relevant C code.\nIt is especially useful for the tricky bit manipulation and shift instructions.\n"
G_PLUGIN_NAME = "Altivec To C: Selected Lines"

#/***************************************************************************************************
#*
#*	This 'PLUGIN' data block is how IDA Pro interfaces with this plugin.
#*
#***************************************************************************************************/

class ActionHandler(idaapi.action_handler_t):

	def __init__(self, callback):

		idaapi.action_handler_t.__init__(self)
		self.callback = callback

	def activate(self, ctx):

		self.callback()
		return 1

	def update(self, ctx):

		return idaapi.AST_ENABLE_ALWAYS

def register_actions():

	actions = [
		{
			'id': 'start:a2c',
			'name': G_PLUGIN_NAME,
			'hotkey': 'F3',
			'comment': G_PLUGIN_COMMENT,
			'callback': PluginMain,
			'menu_location': 'Start a2c'
		},
		{
			'id': 'start:a2c1',
			'name': 'altivec2c unimplemented',
			'hotkey': 'Alt-Shift-F3',
			'comment': G_PLUGIN_COMMENT,
			'callback': PluginMainF,
			'menu_location': 'Start a2c1'
		}
	]

	for action in actions:

		if not idaapi.register_action(idaapi.action_desc_t(
			action['id'], # Must be the unique item
			action['name'], # The name the user sees
			ActionHandler(action['callback']), # The function to call
			action['hotkey'], # A shortcut, if any (optional)
			action['comment'] # A comment, if any (optional)
		)):

			print('Failed to register ' + action['id'])

		if not idaapi.attach_action_to_menu(
			action['menu_location'], # The menu location
			action['id'], # The unique function ID
			0):

			print('Failed to attach to menu '+ action['id'])

class altivec_helper_t(idaapi.plugin_t):
	flags = idaapi.PLUGIN_HIDE
	comment = G_PLUGIN_COMMENT
	help = G_PLUGIN_HELP
	wanted_name = G_PLUGIN_NAME
	wanted_hotkey = "F3"

	def init(self):
		if (idaapi.ph.id == idaapi.PLFM_PPC):
			register_actions()
			idaapi.msg("altivec2c: loaded\n")
			return idaapi.PLUGIN_KEEP

		return idaapi.PLUGIN_SKIP

	def run(self, arg):
		idaapi.msg("altivec2c: run\n")

	def term(self):
		pass

def PLUGIN_ENTRY():
	return altivec_helper_t()
	
#VSX / VSX-2
#def xsabsdp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = abs(vs{:d}[0].double)".format(vsxD, vsxB)
#
#def xsadddp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = vs{:d}[0].double + vs{:d}[0].double".format(vsxD, vsxA, vsxB)
#
#def xsaddsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].float = vs{:d}[0].float + vs{:d}[0].float".format(vsxD, vsxA, vsxB)
#
#def xscmpudp(vsxBf, vsxA, vsxB):
#
#	return "cr{:d} = compare_unordered(vs{:d}[0].double, vs{:d}[0].double)".format(vsxBf, vsxA, vsxB)
#
#def xscpsgndp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = (vs{:d}[0].double & 0x80000000:00000000) | (vs{:d}[0].double & 0x7FFFFFFF:FFFFFFFF)".format(vsxD, vsxA, vsxB)
#
#def xscvdpsp(vsxD, vsxB):
#
#	return "vs{:d}[0].float = dptofp(vs{:d}[0].double)".format(vsxD, vsxB)
#
## fixme
#def xscvdpspn(vsxD, vsxB):
#
#	return "vs{:d}[0].float = dptofp(vs{:d}[0].double)".format(vsxD, vsxB)
#
#def xscvdpsxds(vsxD, vsxB):
#
#	return ""
#
##ok
#def xscvdpsxws(vsxD, vsxB):
#
#	return "vs{:d}[1].word = dptosi32(vs{:d}[0].double)".format(vsxD, vsxB)
#
#def xscvdpuxds(vsxD, vsxB):
#
#	return "vs{:d}[0].doubleword = dptoui64(vs{:d}[0].double)".format(vsxD, vsxB)
#
#def xscvdpuxws(vsxD, vsxB):
#
#	return "vs{:d}[1].word = dptoui32(vs{:d}[0].double)".format(vsxD, vsxB)
#
#def xscvspdp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = dptofp(vs{:d}[0].float)".format(vsxD, vsxB)
#
##fixme
#def xscvspdpn(vsxD, vsxB):
#
#	return "vs{:d}[0].double = dptofp(vs{:d}[0].float)".format(vsxD, vsxB)
#
#def xscvsxddp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = i64todp(vs{:d}[0].doubleword)".format(vsxD, vsxB)
#
#def xscvsxdsp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = fptodp(i64tofp(vs{:d}[0].doubleword))".format(vsxD, vsxB)
#
#def xscvuxddp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = u64todp(vs{:d}[0].doubleword)".format(vsxD, vsxB)
#
#def xscvuxdsp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = fptodp(u64tofp(vs{:d}[0].doubleword))".format(vsxD, vsxB)
#
## todo Manual state this is fp divide, like wtf for half of those ops...
## xsdivsp need work too
#def xsdivdp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = vs{:d}[0].double / vs{:d}[0].double".format(vsxD, vsxA, vsxB)
#
#def xsdivsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = fptodp(dptofp(vs{:d}[0].double / vs{:d}[0].double))".format(vsxD, vsxA, vsxB)
#
#def xsmaddadp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = (vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsmaddmdp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = (vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsmaddasp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double))".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsmaddmsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double))".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsmaxdp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = std::max(vs{:d}[0].double, vs{:d}[0].double)".format(vsxD, vsxA, vsxB)
#
#def xsmindp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = std::min(vs{:d}[0].double, vs{:d}[0].double)".format(vsxD, vsxA, vsxB)
#
#def xsmsubadp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = (vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsmsubmdp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = (vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsmsubasp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double))".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsmsubmsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double))".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsmuldp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = vs{:d}[0].double * vs{:d}[0].double".format(vsxD, vsxA, vsxB)
#
#def xsmulsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = fptodp(dptofp(vs{:d}[0].double * vs{:d}[0].double))".format(vsxD, vsxA, vsxB)
#
#def xsnabsdp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = vs{:d}[0].double | 0x80000000:00000000".format(vsxD, vsxB)
#
#def xsnegdp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = vs{:d}[0].double | ~(vs{:d}[0].double & 0x80000000:00000000 )".format(vsxD, vsxB)
#
#def xsnmaddadp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~((vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double)".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsnmaddmdp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~((vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double)".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsnmaddasp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~(fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double)))".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsnmaddmsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~(fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) + vs{:d}[0].double)))".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsnmsubadp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~((vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double)".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsnmsubmdp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~((vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double)".format(vsxD, vsxA, vsxD, vsxB)
#
#def xsnmsubasp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~(fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double)))".format(vsxD, vsxA, vsxB, vsxD)
#
#def xsnmsubmsp(vsxD, vsxA, vsxB):
#
#	return "vs{:d}[0].double = ~(fptodp(dptofp((vs{:d}[0].double * vs{:d}[0].double) - vs{:d}[0].double)))".format(vsxD, vsxA, vsxD, vsxB)
#
##todo xsrdpi, xsrdpic, xsrdpim, xsrdpip, xsrdpiz
#
#def xsredp(vsxD, vsxB):
#
#	return "vs{:d}[0].double = 1 / vs{:d}[0].double".format(vsxD, vsxB)


