# plugins

repository: cabalgata/cabalgata-silla-de-montar
file: test/test_factories.py
created_at: 2014-11-27 02:10:19+00:00
pushed_at: 2015-02-28 16:42:47+00:00
url:
https://github.com/cabalgata/cabalgata-silla-de-montar/blob/6f1de56f207e55d788d56636f623c0e3ce1aa750/test/test_factories.py

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

from cabalgata.silla import factories
from cabalgata.silla.util import disk


def plugin_entry_points():
    return (
        pkg_resources.EntryPoint.parse('a=data.factories:FactoryA'),
    )


@mock.patch('pkg_resources.iter_entry_points')
def test_load_factory(mock_pkg_resources):
    mock_pkg_resources.return_value = plugin_entry_points()

    with disk.temp_directory() as temp_directory:
        factory = factories.load_factory('a', temp_directory)

        assert factory.versions
        assert factory.definitions

        try:
            factories.load_factory('does_not_exist', temp_directory)

            assert False, 'Should have raised an KeyError'
        except KeyError:
            pass


@mock.patch('pkg_resources.iter_entry_points')
def test_install(mock_pkg_resources):
    mock_pkg_resources.return_value = plugin_entry_points()

    with disk.temp_directory() as temp_directory:
        factory = factories.load_factory('a', temp_directory)

        assert not factory.installed

        factory.install('test', '1.2.3')

        installed = factory.installed
        assert installed['test'] == '1.2.3'

        p = factory.load('test')

        assert p.version == '1.2.3'

        factory.uninstall('test')

        assert not factory.installed


@mock.patch('pkg_resources.iter_entry_points')
def test_start(mock_pkg_resources):
    mock_pkg_resources.return_value = plugin_entry_points()

    with disk.temp_directory() as temp_directory:
        factory = factories.load_factory('a', temp_directory)

        factory.install('test', '1.2.3')

        p = factory.load('test')
        assert not p.running

        p.start()
        assert p.running
        p.stop()
        assert not p.running

        p.start()
        assert p.running
        p.kill()
        assert not p.running

        factory.uninstall('test')


repository: cedricp/ddtplugins
file: card_programming.py
created_at: 2017-07-11 09:00:16+00:00
pushed_at: 2023-02-28 16:39:53+00:00
url:
https://github.com/cedricp/ddtplugins/blob/98a28fb5fd6808a976b15db25b7d23f4478812d7/card_programming.py

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

# Plugin to program keys on Megane II
# (c) 2017
# This is an example plugin


import PyQt5.QtCore as core
import PyQt5.QtWidgets as gui

import ecu
import options

_ = options.translator('ddt4all')

plugin_name = _("Megane/Scenic II card programming")
category = _("Keys")
need_hw = True
ecufile = "UCH_84_J84_03_60"

a8_mess = [19, 29, 39, 4, 24, 46, 31, 12,
           16, 28, 7, 10, 15, 0, 40, 26,
           18, 20, 3, 8, 34, 8, 47, 21,
           2, 25, 38, 6, 4, 22, 14, 33,
           17, 1, 44, 11, 32, 41, 36, 42,
           5, 45, 5, 43, 30, 37, 13, 27]

a8_xor = [0x10, 0x20, 0x20, 0x10, 0x81, 0x88]
a8_xor_2 = [0x10, 0x20, 0x28, 0x10, 0x81, 0x88]


def a8(isk_c):
    '''MC9S12DG256 (Megane 2, Scenic 2)'''
    apv = ''
    isk = isk_c.replace(' ', '').decode('hex')
    if len(isk) != 6:
        return None
    bit_i = ''
    for c in isk:
        bit_i += bin(ord(c))[2:].zfill(8)

    # if not (bit_i[ 4]!=bit_i[23] and bit_i[12]==bit_i[43] and
    #        bit_i[ 4]==bit_i[35] and bit_i[ 8]==bit_i[ 9] and bit_i[ 4]==bit_i[ 5]):
    #  return 'Invalid ISK'

    base = ''
    for b in a8_xor:
        base = base + bin(b)[2:].zfill(8)

    i = 0
    for b in base:
        z = bit_i[a8_mess[i]]
        if b != z:
            z = '1'
        else:
            z = '0'
        base = base[:i] + z + base[i + 1:]
        i += 1

    for i in range(0, 6):
        apv += hex(int(base[i * 8:i * 8 + 8], 2))[2:].zfill(2).upper() + ' '

    return apv


def a8_2(isk_c):
    '''MC9S12DG256 (algo2)(Megane 2, Scenic 2)'''
    apv = ''
    isk = isk_c.replace(' ', '').decode('hex')
    if len(isk) != 6:
        return None
    bit_i = ''
    for c in isk:
        bit_i += bin(ord(c))[2:].zfill(8)

    base = ''
    for b in a8_xor_2:
        base = base + bin(b)[2:].zfill(8)

    i = 0
    for b in base:
        z = bit_i[a8_mess[i]]
        if b != z:
            z = '1'
        else:
            z = '0'
        base = base[:i] + z + base[i + 1:]
        i += 1

    for i in range(0, 6):
        apv += hex(int(base[i * 8:i * 8 + 8], 2))[2:].zfill(2).upper() + ' '

    return apv


def get_isk(ecu_response):
    ecu_response = ecu_response.replace(' ', '').strip()
    return ecu_response[19 * 2:25 * 2]


class CardProg(gui.QDialog):
    def __init__(self):
        super(CardProg, self).__init__()
        options.debug = True
        self.apvok = False
        self.megane_ecu = ecu.Ecu_file(ecufile, True)
        self.start_session_request = self.megane_ecu.requests[u'Start Diagnostic Session']
        self.after_sale_request = self.megane_ecu.requests[u'ACCEDER AU MODE APRES-VENTE']
        self.learn_key_request = self.megane_ecu.requests[u'APPRENDRE BADGE']
        self.key_bits_status = self.megane_ecu.requests[u'Status général des opérations badges Bits']
        self.key_bytes_status = self.megane_ecu.requests[u'Status général des opérations badges Octets']
        self.reserved_frame_request = self.megane_ecu.requests[u'Trame AB: Trame réservée']
        self.learn_validate_request = self.megane_ecu.requests[u'SORTIE DU MODE APV : VALIDATION']
        self.learn_cancel_request = self.megane_ecu.requests[u'SORTIE DU MODE APV : ABANDON']

        self.ecu_connect()
        self.start_diag_session()

        self.apv_access_button = gui.QPushButton(_("ENTER AFTER SALE MODE"))
        self.apv_access_button.clicked.connect(self.set_apv_from_input)
        self.pininput = gui.QLineEdit()
        self.pininput.setText("0" * 12)
        self.pininput.setInputMask("H" * 12)
        self.iskoutput = gui.QLineEdit()
        self.iskoutput.setInputMask("H" * 12)
        self.numkeys_label = gui.QLabel("0")
        self.currentcardide = gui.QLineEdit()
        self.currentcardide.setReadOnly(True)
        self.apv_status = gui.QLabel("")
        self.apv_status.setAlignment(core.Qt.AlignHCenter)
        label = gui.QLabel(_("<font color='red'>MEGANE II CARD PROGRAMMING<br>EXPERIMENTAL : NOT TESTED YET</font>"))
        label.setAlignment(core.Qt.AlignHCenter)
        self.learnbutton = gui.QPushButton(_("LEARN"))
        self.validatebutton = gui.QPushButton(_("VALIDATE"))
        self.cancelbutton = gui.QPushButton(_("CANCEL"))

        self.algocheck = gui.QCheckBox(_("Algo 2"))
        self.algocheck.setChecked(False)

        self.learnbutton.clicked.connect(self.learn_action)
        self.validatebutton.clicked.connect(self.validate_action)
        self.cancelbutton.clicked.connect(self.cancel_action)
        self.iskoutput.textChanged.connect(self.calculate_pin)
        self.algocheck.toggled.connect(self.calculate_pin)

        layout = gui.QGridLayout()
        layout.addWidget(label, 0, 0, 1, 0)
        layout.addWidget(gui.QLabel(_("ISK")), 1, 0)
        layout.addWidget(self.iskoutput, 1, 1)
        layout.addWidget(gui.QLabel(_("PIN")), 2, 0)
        layout.addWidget(self.pininput, 2, 1)
        layout.addWidget(self.algocheck, 3, 1)
        layout.addWidget(self.apv_access_button, 4, 0, 1, 0)
        layout.addWidget(self.apv_status, 5, 0, 1, 0)
        layout.addWidget(gui.QLabel(_("Num key learnt")), 6, 0)
        layout.addWidget(self.numkeys_label, 6, 1)
        layout.addWidget(gui.QLabel(_("CARD IDE")), 7, 0)
        layout.addWidget(self.currentcardide, 7, 1)
        layout.addWidget(self.learnbutton, 8, 0, 1, 0)
        layout.addWidget(self.validatebutton, 9, 0)
        layout.addWidget(self.cancelbutton, 9, 1)

        self.setLayout(layout)

        # I have to use a timer here to avoid ECU from escaping session mode
        # A shot every 1.5 seconds is enough, normally
        self.tester_timer = core.QTimer()
        self.tester_timer.setSingleShot(False)
        self.tester_timer.setInterval(1500)
        self.tester_timer.timeout.connect(self.check_all)

        isk = self.get_isk()
        self.iskoutput.setText(isk)

        self.tester_timer.start()

    def calculate_pin(self):
        ISK = ''.join(str(ord(c)) for c in str(self.iskoutput.text()))
        if len(ISK) == 12:
            if self.algocheck.checkState():
                PIN = a8_2(ISK)
            else:
                PIN = a8(ISK)
            if PIN is not None:
                self.pininput.setText(PIN)
                self.iskoutput.setStyleSheet("color: green;")
                return

        self.pininput.setText("000000000000")
        self.iskoutput.setStyleSheet("color: red;")

    def learn_action(self):
        self.learn_key_request.send_request()

    def validate_action(self):
        self.learn_validate_request.send_request()

    def cancel_action(self):
        self.learn_cancel_request.send_request()

    def get_isk(self):
        # No dataitem for ISK :(
        # Need to decode by myself
        if options.simulation_mode:
            reply = "61 AB 02 FC 0D 08 51 4C 86 55 54 00 00 00 00 00 00 00 00 8D E8 EE 16 79 D3 C9 A7 A7 CC " \
                    "F6 AC 00 00 00 00 00 00 2A"
        else:
            reply = options.elm.request(self.reserved_frame_request.sentbytes)

        if reply.startswith('7F') or reply.startswith('WRONG'):
            options.main_window.logview.append(_("Cannot get ISK, check connections and UCH compatibility"))
            return

        isk = reply.replace(' ', '').strip()
        return get_isk(isk)

    def ecu_connect(self):
        connection = self.megane_ecu.connect_to_hardware()
        if not connection:
            options.main_window.logview.append(_("Cannot connect to ECU"))
            self.finished()

    def check_all(self):
        self.check_apv_status()
        self.check_num_key_learnt()

    def set_apv_from_input(self):
        apv = ''.join(str(ord(c)) for c in str(self.pininput.text()))
        if len(apv) != 12:
            return
        self.after_sale_request.send_request({u'Code APV': apv})

    def enable_buttons(self, enable):
        self.learnbutton.setEnabled(enable)
        self.validatebutton.setEnabled(enable)
        self.cancelbutton.setEnabled(enable)

    def check_apv_status(self):
        key_bits_status_values = self.key_bits_status.send_request({}, "61 06 21 21 20 00 00 00 08 00 00 80 03")

        value_apv_ok = key_bits_status_values[u'VSC Code APV_Reconnu']
        value_apv_reaff = key_bits_status_values[u'VSC ModeAPV_ReaffArmé']
        value_apv_uch_reaff = key_bits_status_values[u'VSC ModeAPV_AppUCH_Armé']

        if value_apv_ok == '0':
            self.apv_status.setText(_("<font color='red'>PIN CODE NOT RECOGNIZED</font>"))
            self.apvok = False
            self.enable_buttons(False)
            return

        if value_apv_reaff == '1':
            self.apv_status.setText(_("<font color='green'>PIN CODE OK / KEY LEARNING</font>"))
            self.enable_buttons(True)
            self.apvok = True
            return

        elif value_apv_uch_reaff == '1':
            self.apv_status.setText(_("<font color='green'>PIN CODE OK / UCH LEARNING</font>"))
            self.enable_buttons(True)
            self.apvok = True
            return

        self.apvok = False
        self.enable_buttons(False)

        self.apv_status.setText("<font color='red'>UCH NOT READY</font>")

    def check_num_key_learnt(self):
        key_bytes_status_value = self.key_bytes_status.send_request({}, "61 08 00 00 00 00 00 00 0A 00 00 00 00 00 00 "
                                                                        "00 00 00 00 00 00 00 00 00 00 02 00 00 01 2C "
                                                                        "00 00 01 2C 00 00 2C 00 00 01 2C")

        num_key_learnt = key_bytes_status_value[u'VSC NbTotalDeBadgeAppris']
        current_key_ide = key_bytes_status_value[u'VSC Code_IDE']

        self.numkeys_label.setText(num_key_learnt)
        self.currentcardide.setText(current_key_ide)

    def start_diag_session(self):
        sds_request = self.megane_ecu.requests[u"Start Diagnostic Session"]
        sds_stream = " ".join(sds_request.build_data_stream({}))
        if options.simulation_mode:
            print("SdS stream", sds_stream)
            return
        options.elm.start_session_can(sds_stream)


def plugin_entry():
    cp = CardProg()
    cp.exec_()


repository: cbwang505/DIYDynamoRIO
file: MyLighthouse/lighthouse/ida_loader.py
wanted_name: Lighthouse
comment: Code Coverage Explorer
created_at: 2020-12-22 02:58:47+00:00
pushed_at: 2022-04-06 06:10:30+00:00
description:
动态二进制插桩框架DynamoRIO通过将程序代码进行反复插桩(Instrumentation)执行构建了源程序代码与操纵代码之间的桥梁,使DynamoRIO的客户端编写者能够在更高的层面上驾驭原有的程序代码.虽然程序的载体还是被编译成原生的汇编指令集执行,但是不管是原生代码还是程序行为逻辑DynamoRIO为我们提供丰富的API已经把这些封装成了足够友好操作方式暴露给客户端编写者使用,用户可以透明的修改原有的程序代码(HotPatch),执行追踪,Hook,调试,模拟等高级运行时操纵(Runtime Code Manipulation )技术.本文主要分析DynamoRIO插桩的主要流程和实现原理,深入源码片段中几个有意思的小节体现作者构思的巧妙之处,并附加相关demo分析让读者加深对DynamoRIO的认识.

url:
https://github.com/cbwang505/DIYDynamoRIO/blob/93efa58e802e17115f7623ef4e38a1e2ac856822/MyLighthouse/lighthouse/ida_loader.py

content:
import logging

import idaapi
from lighthouse.util.log import lmsg
from lighthouse.ida_integration import LighthouseIDA

logger = logging.getLogger("Lighthouse.IDA.Loader")

#------------------------------------------------------------------------------
# Lighthouse IDA Loader
#------------------------------------------------------------------------------
#
#    This file contains a stub 'plugin' class for Lighthouse as required by
#    IDA Pro. Practically speaking, there should be little to *no* logic placed
#    in this file because it is disassembler-specific.
#
#    When IDA Pro is starting up, it will import all python files placed in its
#    root plugin folder. It will then attempt to call PLUGIN_ENTRY() on each of
#    the imported 'plugins'. We import PLUGIN_ENTRY into lighthouse_plugin.py
#    so that IDA can see it.
#
#    PLUGIN_ENTRY() is expected to return a plugin object (LighthouseIDAPlugin)
#    derived from idaapi.plugin_t. IDA will register the plugin, and interface
#    with the plugin object to load / unload the plugin at certain times, per
#    its configuration (flags, hotkeys).
#
#    There should be virtually no reason for you to modify this file.
#

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

class LighthouseIDAPlugin(idaapi.plugin_t):
    """
    The IDA plugin stub for Lighthouse.
    """

    #
    # Plugin flags:
    # - PLUGIN_MOD: Lighthouse is a plugin that may modify the database
    # - PLUGIN_PROC: Load/unload Lighthouse when an IDB opens / closes
    # - PLUGIN_HIDE: Hide Lighthouse from the IDA plugin menu
    #

    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_MOD | idaapi.PLUGIN_HIDE
    comment = "Code Coverage Explorer"
    help = ""
    wanted_name = "Lighthouse"
    wanted_hotkey = ""

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

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """
        try:
            self._lighthouse = LighthouseIDA()
            self._lighthouse.load()
        except Exception as e:
            lmsg("Failed to initialize Lighthouse")
            logger.exception("Exception details:")
            return idaapi.PLUGIN_SKIP

        # tell IDA to keep the plugin loaded (everything is okay)
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        idaapi.warning("Lighthouse cannot be run as a script in IDA.")

    def term(self):
        """
        This is called by IDA when it is unloading the plugin.
        """
        try:
            self._lighthouse.unload()
            self._lighthouse = None
        except Exception as e:
            logger.exception("Failed to cleanly unload Lighthouse from IDA.")


repository: JeffJerseyCow/qemu2drcov
file: lighthouse/plugin/lighthouse/ida_loader.py
description: QEMU to drcov trace file
wanted_name: Lighthouse
comment: Code Coverage Explorer
created_at: 2019-01-21 08:33:22+00:00
pushed_at: 2020-11-21 21:27:38+00:00
url:
https://github.com/JeffJerseyCow/qemu2drcov/blob/38443a7bef6b08079021e91da00b0b2d0e8818a5/lighthouse/plugin/lighthouse/ida_loader.py

content:
import logging

import idaapi
from lighthouse.util.log import lmsg
from lighthouse.ida_integration import LighthouseIDA

logger = logging.getLogger("Lighthouse.IDA.Loader")

#------------------------------------------------------------------------------
# Lighthouse IDA Loader
#------------------------------------------------------------------------------
#
#    This file contains a stub 'plugin' class for Lighthouse as required by
#    IDA Pro. Practically speaking, there should be little to *no* logic placed
#    in this file because it is disassembler-specific.
#
#    When IDA Pro is starting up, it will import all python files placed in its
#    root plugin folder. It will then attempt to call PLUGIN_ENTRY() on each of
#    the imported 'plugins'. We import PLUGIN_ENTRY into lighthouse_plugin.py
#    so that IDA can see it.
#
#    PLUGIN_ENTRY() is expected to return a plugin object (LighthouseIDAPlugin)
#    derived from idaapi.plugin_t. IDA will register the plugin, and interface
#    with the plugin object to load / unload the plugin at certain times, per
#    its configuration (flags, hotkeys).
#
#    There should be virtually no reason for you to modify this file.
#

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

class LighthouseIDAPlugin(idaapi.plugin_t):
    """
    The IDA plugin stub for Lighthouse.
    """

    #
    # Plugin flags:
    # - PLUGIN_MOD: Lighthouse is a plugin that may modify the database
    # - PLUGIN_PROC: Load/unload Lighthouse when an IDB opens / closes
    # - PLUGIN_HIDE: Hide Lighthouse from the IDA plugin menu
    #

    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_MOD | idaapi.PLUGIN_HIDE
    comment = "Code Coverage Explorer"
    help = ""
    wanted_name = "Lighthouse"
    wanted_hotkey = ""

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

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """
        try:
            self._lighthouse = LighthouseIDA()
            self._lighthouse.load()
        except Exception as e:
            lmsg("Failed to initialize Lighthouse")
            logger.exception("Exception details:")
            return idaapi.PLUGIN_SKIP

        # tell IDA to keep the plugin loaded (everything is okay)
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        idaapi.warning("Lighthouse cannot be run as a script in IDA.")

    def term(self):
        """
        This is called by IDA when it is unloading the plugin.
        """
        try:
            self._lighthouse.unload()
            self._lighthouse = None
        except Exception as e:
            logger.exception("Failed to cleanly unload Lighthouse from IDA.")


repository: Jinmo/palette-plugins
file: lighthouse/plugins/lighthouse/ida_loader.py
description: Palette plugins
wanted_name: Lighthouse
comment: Code Coverage Explorer
created_at: 2019-03-03 07:45:35+00:00
pushed_at: 2019-04-28 13:39:53+00:00
url:
https://github.com/Jinmo/palette-plugins/blob/7b00707fedbe1031aa9b420ffbe4206db7883ebd/lighthouse/plugins/lighthouse/ida_loader.py


repository: mahmoudimus/.idapro
file: plugins/cvutils-gotooffset.py
comment: Go to an offset.
created_at: 2025-02-16 21:33:20+00:00
pushed_at: 2025-04-09 18:15:30+00:00
url:
https://github.com/mahmoudimus/.idapro/blob/e0bf0d58fb6de4eb6af0b87ffbd869d8c7deef08/plugins/cvutils-gotooffset.py

content:
#------------------------------------------------------------------------------
# IDA Plugin to jump to an offset from the Imagebase.
# Copy the 'cvutils-getoffset.py' into the plugins directory of IDA
#------------------------------------------------------------------------------

VERSION = '1.1.0'
__AUTHOR__ = 'cra0'

PLUGIN_NAME = "Go To Offset"
PLUGIN_HOTKEY = "Shift+G"


  
import os
import sys
import idc
import idaapi
import idautils
import string


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

idaver_74newer = (major == 7 and minor >= 4)
idaver_8newer = (major >= 8)

if idaver_74newer or idaver_8newer:
    newer_version_compatible = True
else:
    newer_version_compatible = False

if newer_version_compatible:
    #IDA 7.4+
    #https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml
    import ida_ida
    import ida_kernwin
    
if using_pyqt5:
    import PyQt5.QtGui as QtGui
    import PyQt5.QtCore as QtCore
    import PyQt5.QtWidgets as QtWidgets
    from PyQt5.Qt import QApplication

else:
    import PySide.QtGui as QtGui
    import PySide.QtCore as QtCore
    QtWidgets = QtGui
    QtCore.pyqtSignal = QtCore.Signal
    QtCore.pyqtSlot = QtCore.Slot
    from PySide.QtGui import QApplication
    


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

class cvutils_gotooffset(idaapi.plugin_t):

    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
    comment = "Go to an offset."
    help = "Use the shortcut key to open the goto dialog box."
    wanted_name = PLUGIN_NAME
    wanted_hotkey = PLUGIN_HOTKEY

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

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

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

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

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

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


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


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


    #--------------------------------------------------------------------------
    # IDA Actions
    #--------------------------------------------------------------------------

    ACTION_GET_OFFSET  = "prefix:goto_offset"


    def _init_action_goto_offset(self):
        """
        Register the copy bytes action with IDA.
        """   
        vaction_desc = "Go to offset."
        if (sys.version_info > (3, 0)):
            # Describe the action using python3 copy
            action_desc = idaapi.action_desc_t(
                self.ACTION_GET_OFFSET,                                     # The action name.
                "Go to offset",                                             # The action text.
                IDACtxEntry(goto_offset),                                # The action handler.
                PLUGIN_HOTKEY,                                              # Optional: action shortcut
                vaction_desc,                                               # Optional: tooltip
                31                                                          # Copy icon
            )
        else:
            # Describe the action using python2 copy
            action_desc = idaapi.action_desc_t(
                self.ACTION_GET_OFFSET,                                 # The action name.
                "Go to offset",                                         # The action text.
                IDACtxEntry(goto_offset),                            # The action handler.
                PLUGIN_HOTKEY,                                          # Optional: action shortcut
                vaction_desc,                                           # Optional: tooltip
                31                                                      # Copy icon
            )


        # register the action with IDA
        assert idaapi.register_action(action_desc), "Action registration failed"


    def _del_action_goto_offset(self):
        """
        Delete the bulk prefix action from IDA.
        """
        idaapi.unregister_action(self.ACTION_GET_OFFSET)

#------------------------------------------------------------------------------
# Display a warning message box
#------------------------------------------------------------------------------
def display_warning(message): 
    if newer_version_compatible:
        return idaapi.warning(message)
    else:
        return idc.Warning(message)
   
#------------------------------------------------------------------------------
# Jump to a certain address
#------------------------------------------------------------------------------
def jump_to_address(jump_address): 
    if newer_version_compatible:
        ida_kernwin.jumpto(jump_address)
    else:
        idc.Jump(jump_address)


#------------------------------------------------------------------------------
# Image Min EA
#------------------------------------------------------------------------------
def get_minEA(): 
    if newer_version_compatible:
        return ida_ida.inf_get_min_ea()
    else:
        return idc.MinEA()

#------------------------------------------------------------------------------
# IsBadAddress
#------------------------------------------------------------------------------

def is_hex(s):
     hex_digits = set(string.hexdigits)
     # if s is long, then it is faster to check against a set
     return all(c in hex_digits for c in s)

def is_hex(s):
    try:
        int(s, 16)
        return True
    except ValueError:
        return False

def isvalid_address(ea):
    """Check if the given address is valid
    
    Arguments:
        ea: The linear address to check.
    """   
    if (ea == idaapi.BADADDR):
        print("[%x] BADADDR" % ea)
        return 0
        
    pe_min_ea = get_minEA()
       
    if (ea < pe_min_ea):
        print("[%x] is lower than MinEA [%x]" % (ea, pe_min_ea))
        return 0
               
    if not idaapi.getseg(ea):
        print("[%x] getseg failed" % ea)
        return 0
    
    return 1


#------------------------------------------------------------------------------
# Go to offset
#------------------------------------------------------------------------------

def goto_offset():
    hint_min_offset = get_minEA() - idaapi.get_imagebase()
    
    #Use string for now, force them to use hex as the offset
    offset_value=None
    if newer_version_compatible:
        offset_value = ida_kernwin.ask_str("0x%x" % hint_min_offset, 0, "To Offset[HEX]:")    
    else:
        offset_value = idc.AskStr("0x%x" % hint_min_offset, "To Offset[HEX]:")

    if not offset_value:
        print("No value was provided. Ignoring")
        return
    
    if not is_hex(offset_value):
        print("Bad input; It doesn't contain valid hex")
        display_warning("Bad Input!")
        return
        
    offset_hex = int(offset_value, 16)
    if offset_hex == 0:
        display_warning("Input is Invalid!")
        return
       
    image_base = idaapi.get_imagebase()
    jump_address = image_base + offset_hex
    
    if (isvalid_address(jump_address)):
        print ("Offset [%x] =-> Address [0x%x]" % (offset_hex, jump_address))
        jump_to_address(jump_address)
    else:
        display_warning("Bad Offset!")
    
    return

#------------------------------------------------------------------------------
# IDA ctxt
#------------------------------------------------------------------------------

class IDACtxEntry(idaapi.action_handler_t):

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

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

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


repository: Sentinel-One/brick
file: deps/alleycat.py
wanted_name: AlleyCat
created_at: 2021-06-07 23:12:42+00:00
pushed_at: 2024-06-18 14:35:08+00:00
url:
https://github.com/Sentinel-One/brick/blob/b30c9bd9b34d3b466366d612b8ac1aa13c1cda81/deps/alleycat.py

content:
import sys
import idc
import time
import idaapi
import idautils

from shims import ida_shims

# This limits the depth of any individual path, as well as the maximum
# number of paths that will be searched; this is needed for practical
# reasons, as IDBs with tens of thousands of functions take a long time
# to exhaust all possible paths without some practical limitation.
#
# This is global so it's easy to change from the IDAPython prompt.
ALLEYCAT_LIMIT = 10000


def add_to_namespace(namespace, source, name, variable):
    '''
    Add a variable to a different namespace, likely __main__.
    '''
    import importlib

    importer_module = sys.modules[namespace]
    if source in list(sys.modules.keys()):
        if not (sys.version_info.major == 3 and sys.version_info.minor >= 4):
            import imp
            imp.reload(sys.modules[source])
        else:
            importlib.reload(sys.modules[source])
    else:
        m = importlib.import_module(source, None)
        sys.modules[source] = m

    setattr(importer_module, name, variable)


class AlleyCatException(Exception):
    pass


class AlleyCat(object):
    '''
    Class which resolves code paths. This is where most of the work is done.
    '''

    def __init__(self, start, end, quiet=False):
        '''
        Class constructor.

        @start - The start address.
        @end   - The end address.

        Returns None.
        '''
        global ALLEYCAT_LIMIT
        self.limit = ALLEYCAT_LIMIT
        self.paths = []
        self.quiet = quiet

        # We work backwards via xrefs, so we start at the end and end at the
        # start
        if not self.quiet:
            print("Generating call paths from %s to %s..." % (self._name(end),
                                                              self._name(start)))
        self._build_paths(start, end)

    def _name(self, ea):
        name = ida_shims.get_name(ea)
        if not name:
            name = ida_shims.get_func_off_str(ea)
            if not name:
                name = '0x%X' % ea
        return name

    def _add_path(self, path):
        if path not in self.paths:
            self.paths.append(path)

    def _build_paths(self, start, end=idc.BADADDR):
        partial_paths = [[start]]

        # Loop while there are still unresolve paths and while all path sizes
        # have not exceeded ALLEYCAT_LIMIT
        while partial_paths and \
                len(self.paths) < self.limit and \
                len(partial_paths) < self.limit:
            callers = set()

            # Callee is the last entry of the first path in partial paths.
            # The first path list will change as paths are completed and
            # popped from the list.
            callee = partial_paths[0][-1]

            # Find all unique functions that reference the callee, assuming this
            # path has not exceeded ALLEYCAT_LIMIT.
            if len(partial_paths[0]) < self.limit:
                for xref in idautils.XrefsTo(callee):
                    caller = self._get_code_block(xref.frm)
                    if caller:
                        start_ea = ida_shims.start_ea(caller)

                        if start_ea not in callers:
                            callers.add(start_ea)

            # If there are callers to the callee, remove the callee's current
            # path and insert new ones with the new callers appended.
            if callers:
                base_path = partial_paths.pop(0)
                for caller in callers:

                    # Don't want to loop back on ourselves in the same path
                    if caller in base_path:
                        continue

                    # If we've reached the desired end node, don't go any
                    # further down this path
                    if caller == end:
                        self._add_path((base_path + [caller])[::-1])
                    else:
                        partial_paths.append(base_path + [caller])
            # Else, our end node is not in this path, so don't include it in the
            # finished path list.
            elif end not in partial_paths[0]:
                partial_paths.pop(0)
            # If there were no callers then this path has been exhausted and
            # should be popped from the partial path list into the finished path list.
            elif end in partial_paths[0]:
                # Paths start with the end function and end with the start
                # function; reverse it.
                self._add_path(partial_paths.pop(0)[::-1])

    def _get_code_block(self, ea):
        return idaapi.get_func(ea)


class AlleyCatFunctionPaths(AlleyCat):

    def __init__(self, start_ea, end_ea, quiet=False):

        # We work backwards via xrefs, so we start at the end and end at the start
        try:
            func = idaapi.get_func(end_ea)
            start = ida_shims.start_ea(func)
        except:
            raise AlleyCatException("Address 0x%X is not part of a function!" %
                                    end_ea)
        try:
            func = idaapi.get_func(start_ea)
            end = ida_shims.start_ea(func)
        except:
            end = idc.BADADDR

        super(AlleyCatFunctionPaths, self).__init__(start, end, quiet)


class AlleyCatCodePaths(AlleyCat):

    def __init__(self, start_ea, end_ea, quiet=False):
        end_func = idaapi.get_func(end_ea)
        start_func = idaapi.get_func(start_ea)

        if not start_func:
            raise AlleyCatException("Address 0x%X is not part of a function!" %
                                    start_ea)
        if not end_func:
            raise AlleyCatException("Address 0x%X is not part of a function!" %
                                    end_ea)

        start_func_ea = ida_shims.start_ea(start_func)
        end_func_ea = ida_shims.start_ea(end_func)

        if start_func_ea != end_func_ea:
            raise AlleyCatException("The start and end addresses are not part "
                                    "of the same function!")

        self.func = start_func
        self.blocks = [block for block in idaapi.FlowChart(self.func)]

        # We work backwards via xrefs, so we start at the end and end at the start
        end_block = self._get_code_block(start_ea)
        start_block = self._get_code_block(end_ea)

        if not end_block:
            raise AlleyCatException("Failed to find the code block associated "
                                    "with address 0x%X" % start_ea)
        if not start_block:
            raise AlleyCatException("Failed to find the code block associated "
                                    "with address 0x%X" % end_ea)

        start_block_ea = ida_shims.start_ea(start_block)
        end_block_ea = ida_shims.start_ea(end_block)

        super(AlleyCatCodePaths, self).__init__(
            start_block_ea, end_block_ea, quiet)

    def _get_code_block(self, ea):
        for block in self.blocks:
            start_ea = ida_shims.start_ea(block)
            end_ea = ida_shims.end_ea(block)
            if start_ea <= ea and end_ea > ea:
                return block
        return None


# Everything below here is just IDA UI/Plugin stuff


class AlleyCatGraphHistory(object):
    '''
    Manages include/exclude graph history.
    '''

    INCLUDE_ACTION = 0
    EXCLUDE_ACTION = 1

    def __init__(self):
        self.history = []
        self.includes = []
        self.excludes = []
        self.history_index = 0
        self.include_index = 0
        self.exclude_index = 0

    def reset(self):
        self.history = []
        self.includes = []
        self.excludes = []
        self.history_index = 0
        self.include_index = 0
        self.exclude_index = 0

    def update_history(self, action):
        if self.excludes and len(self.history)-1 != self.history_index:
            self.history = self.history[0:self.history_index+1]
        self.history.append(action)
        self.history_index = len(self.history)-1

    def add_include(self, obj):
        if self.includes and len(self.includes)-1 != self.include_index:
            self.includes = self.includes[0:self.include_index+1]
        self.includes.append(obj)
        self.include_index = len(self.includes)-1
        self.update_history(self.INCLUDE_ACTION)

    def add_exclude(self, obj):
        if len(self.excludes)-1 != self.exclude_index:
            self.excludes = self.excludes[0:self.exclude_index+1]
        self.excludes.append(obj)
        self.exclude_index  = len(self.excludes)-1
        self.update_history(self.EXCLUDE_ACTION)

    def get_includes(self):
        return set(self.includes[0:self.include_index+1])

    def get_excludes(self):
        return set(self.excludes[0:self.exclude_index+1])

    def undo(self):
        if self.history:
            if self.history[self.history_index] == self.INCLUDE_ACTION:
                if self.include_index >= 0:
                    self.include_index -= 1
            elif self.history[self.history_index] == self.EXCLUDE_ACTION:
                if self.exclude_index >= 0:
                    self.exclude_index -= 1

            self.history_index -= 1
            if self.history_index < 0:
                self.history_index = 0

    def redo(self):
        self.history_index += 1
        if self.history_index >= len(self.history):
            self.history_index = len(self.history)-1

        if self.history[self.history_index] == self.INCLUDE_ACTION:
            if self.include_index < len(self.includes)-1:
                self.include_index += 1
        elif self.history[self.history_index] == self.EXCLUDE_ACTION:
            if self.exclude_index < len(self.excludes)-1:
                self.exclude_index += 1


class AlleyCatGraph(idaapi.GraphViewer):
    '''
    Displays the graph and manages graph actions.
    '''
    def __init__(self, results, title="AlleyCat Graph"):
        idaapi.GraphViewer.__init__(self, title)
        self.results = results

        self.nodes_ea2id = {}
        self.nodes_id2ea = {}
        self.edges = {}
        self.end_nodes = []
        self.edge_nodes = []
        self.start_nodes = []

        self.history = AlleyCatGraphHistory()
        self.include_on_click = False
        self.exclude_on_click = False

        self.cmd_undo = None
        self.cmd_redo = None
        self.cmd_reset = None
        self.cmd_exclude = None
        self.cmd_include = None
        self.cmd_unhighlight = None

    def Show(self):
        '''
        Display the graph.

        Returns True on success, False on failure.
        '''
        if not idaapi.GraphViewer.Show(self):
            return False
        else:
            self.cmd_undo = self.AddCommand("Undo", "")
            self.cmd_redo = self.AddCommand("Redo", "")
            self.cmd_reset = self.AddCommand("Reset graph", "")
            self.cmd_exclude = self.AddCommand("Exclude node", "")
            self.cmd_include = self.AddCommand("Include node", "")
            self.cmd_unhighlight = self.AddCommand(
                "Temporarily un-highlight all paths", "")
            return True

    def OnRefresh(self):
        # Clear the graph before refreshing
        self.clear()
        self.nodes_ea2id = {}
        self.nodes_id2ea = {}
        self.edges = {}
        self.end_nodes = []
        self.edge_nodes = []
        self.start_nodes = []

        includes = self.history.get_includes()
        excludes = self.history.get_excludes()

        for path in self.results:
            parent_node = None

            # Check to see if this path contains all nodes marked for explicit
            # inclusion
            if (set(path) & includes) != includes:
                continue

            # Check to see if this path contains any nodes marked for explicit
            # exclusion
            if (set(path) & excludes) != set():
                continue

            for ea in path:
                # If this node already exists, use its existing node ID
                if ea in self.nodes_ea2id:
                    this_node = self.nodes_ea2id[ea]
                # Else, add this node to the graph
                else:
                    this_node = self.AddNode(self.get_name_by_ea(ea))
                    self.nodes_ea2id[ea] = this_node
                    self.nodes_id2ea[this_node] = ea

                # If there is a parent node, add an edge between the parent node
                # and this one
                if parent_node is not None:
                    self.AddEdge(parent_node, this_node)
                    if this_node not in self.edges[parent_node]:
                        self.edges[parent_node].append(this_node)

                # Update the parent node for the next loop
                parent_node = this_node
                if parent_node not in self.edges:
                    self.edges[parent_node] = []

                # Highlight this node in the disassembly window
                self.highlight(ea)

            try:
                # Track the first, last, and next to last nodes in each path for
                # proper colorization in self.OnGetText.
                self.start_nodes.append(self.nodes_ea2id[path[0]])
                self.end_nodes.append(self.nodes_ea2id[path[-1]])
                self.edge_nodes.append(self.nodes_ea2id[path[-2]])
            except:
                pass

        return True

    def OnGetText(self, node_id):
        color = idc.DEFCOLOR

        if node_id in self.edge_nodes:
            color = 0x00ffff
        elif node_id in self.start_nodes:
            color = 0x00ff00
        elif node_id in self.end_nodes:
            color = 0x0000ff

        return self[node_id], color

    def OnHint(self, node_id):
        hint = ""

        try:
            for edge_node in self.edges[node_id]:
                hint += "%s\n" % self[edge_node]
        except:
            pass

        return hint

    def OnCommand(self, cmd_id):
        if self.cmd_undo == cmd_id:
            if self.include_on_click or self.exclude_on_click:
                self.include_on_click = False
                self.exclude_on_click = False
            else:
                self.history.undo()
            self.Refresh()
        elif self.cmd_redo == cmd_id:
            self.history.redo()
            self.Refresh()
        elif self.cmd_include == cmd_id:
            self.include_on_click = True
        elif self.cmd_exclude == cmd_id:
            self.exclude_on_click = True
        elif self.cmd_reset == cmd_id:
            self.include_on_click = False
            self.exclude_on_click = False
            self.history.reset()
            self.Refresh()
        elif self.cmd_unhighlight == cmd_id:
            self.unhighlight_all()

    def OnClick(self, node_id):
        if self.include_on_click:
            self.history.add_include(self.nodes_id2ea[node_id])
            self.include_on_click = False
        elif self.exclude_on_click:
            self.history.add_exclude(self.nodes_id2ea[node_id])
            self.exclude_on_click = False
        self.Refresh()

    def OnDblClick(self, node_id):
        xref_locations = []
        node_ea = self.get_ea_by_name(self[node_id])

        if node_id in self.edges:
            for edge_node_id in self.edges[node_id]:

                edge_node_name = self[edge_node_id]
                edge_node_ea = self.get_ea_by_name(edge_node_name)

                if edge_node_ea != idc.BADADDR:
                    for xref in idautils.XrefsTo(edge_node_ea):
                        # Is the specified node_id the source of this xref?
                        if self.match_xref_source(xref, node_ea):
                            xref_locations.append((xref.frm, edge_node_ea))

        if xref_locations:
            xref_locations.sort()

            print("")
            print("Path Xrefs from %s:" % self[node_id])
            print("-" * 100)
            for (xref_ea, dst_ea) in xref_locations:
                print("%-50s  =>  %s" % (self.get_name_by_ea(xref_ea),
                                         self.get_name_by_ea(dst_ea)))
            print("-" * 100)
            print("")

            ida_shims.jumpto(xref_locations[0][0])
        else:
            ida_shims.jumpto(node_ea)

    def OnClose(self):
        if ida_shims.ask_yn(1, "Path nodes have been highlighted in the "
                               "disassembly window. Undo highlighting?") == 1:
            self.unhighlight_all()

    def match_xref_source(self, xref, source):
        return ((xref.type != idc.fl_F) and
                (ida_shims.get_func_attr(xref.frm, idc.FUNCATTR_START) == source))

    def get_ea_by_name(self, name):
        '''
        Get the address of a location by name.

        @name - Location name

        Returns the address of the named location, or idc.BADADDR on failure.
        '''
        # This allows support of the function offset style names (e.g., main+0C)
        ea = 0
        if '+' in name:
            (func_name, offset) = name.split('+')
            base_ea = ida_shims.get_name_ea_simple(func_name)
            if base_ea != idc.BADADDR:
                try:
                    ea = base_ea + int(offset, 16)
                except:
                    ea = idc.BADADDR
        else:
            ea = ida_shims.get_name_ea_simple(name)
            if ea == idc.BADADDR:
                try:
                    ea = int(name, 0)
                except:
                    ea = idc.BADADDR

        return ea

    def clear(self):
        # Clears the graph and unhighlights the disassembly
        self.Clear()
        self.unhighlight_all()

    def get_name_by_ea(self, ea):
        '''
        Get the name of the specified address.

        @ea - Address.

        Returns a name for the address, one of idc.Name, idc.GetFuncOffset or
        0xXXXXXXXX.
        '''
        name = ida_shims.get_name(ea)
        if not name:
            name = ida_shims.get_func_off_str(ea)
            if not name:
                name = "0x%X" % ea
        return name

    def colorize_node(self, ea, color):
        # Colorizes an entire code block
        func = idaapi.get_func(ea)
        if func:
            for block in idaapi.FlowChart(func):
                block_start_ea = ida_shims.start_ea(block)
                block_end_ea = ida_shims.end_ea(block)

                if block_start_ea <= ea and block_end_ea > ea:
                    ea = block_start_ea
                    while ea < block_end_ea:
                        idaapi.set_item_color(ea, color)
                        ea = ida_shims.next_head(ea)
                    break

    def highlight(self, ea):
        # Highlights an entire code block
        self.colorize_node(ea, 0x00FF00)

    def unhighlight(self, ea):
        # Unhighlights an entire code block
        self.colorize_node(ea, idc.DEFCOLOR)

    def unhighlight_all(self):
        # Unhighlights all code blocks
        for path in self.results:
            for ea in path:
                self.unhighlight(ea)


class AlleyCatPaths(object):
    def _current_function(self):
        function = idaapi.get_func(ida_shims.get_screen_ea())
        return ida_shims.start_ea(function)

    def _find_and_plot_paths(self, sources, targets, klass=AlleyCatFunctionPaths):
        results = []

        for target in targets:
            for source in sources:
                s = time.time()
                r = klass(source, target).paths
                e = time.time()
                print("Found %d paths in %f seconds." % (len(r), (e-s)))

                if r:
                    results += r
                else:
                    name = ida_shims.get_name(target)
                    if not name:
                        name = "0x%X" % target
                    print("No paths found to", name)

        if results:
            # Be sure to close any previous graph before creating a new one.
            # Failure to do so may crash IDA.
            try:
                self.graph.Close()
            except:
                pass

            self.graph = AlleyCatGraph(results, 'Path Graph')
            self.graph.Show()

    def _get_user_selected_functions(self, many=False):
        functions = []
        ea = ida_shims.get_screen_ea()
        try:
            current_function = ida_shims.get_func_attr(ea, idc.FUNCATTR_START)
        except:
            current_function = None

        while True:
            function = ida_shims.choose_func(
                "Select a function and click 'OK' until all functions have "
                "been selected. When finished, click 'Cancel' to display the "
                "graph.")

            if ida_shims.get_screen_ea() != ea:
                ida_shims.jumpto(ea)

            if not function or \
                    function == idc.BADADDR or function == current_function:
                break
            elif function not in functions:
                functions.append(function)

            if not many:
                break

        return functions

    def FindPathsToCodeBlock(self):
        target = ida_shims.get_screen_ea()
        source = self._current_function()

        if source:
            self._find_and_plot_paths(
                [source], [target], klass=AlleyCatCodePaths)

    def FindPathsToMany(self):
        source = self._current_function()

        if source:
            targets = self._get_user_selected_functions(many=True)
            if targets:
                self._find_and_plot_paths([source], targets)

    def FindPathsFromMany(self):
        target = self._current_function()

        if target:
            sources = self._get_user_selected_functions(many=True)
            if sources:
                self._find_and_plot_paths(sources, [target])


# Helper functions to execute commands selected from dropdown menus.
# args parameter is required for IDA version < 7.0
def find_paths_from_many(arg=None):
    AlleyCatPaths().FindPathsFromMany()


def find_paths_to_many(arg=None):
    AlleyCatPaths().FindPathsToMany()


def find_paths_to_code_block(args=None):
    AlleyCatPaths().FindPathsToCodeBlock()


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

        def activate(self, ctx):
            find_paths_from_many()
            return 1

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


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

        def activate(self, ctx):
            find_paths_to_many()
            return 1

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

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

        def activate(self, ctx):
            find_paths_to_code_block()
            return 1

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


class idapathfinder_t(idaapi.plugin_t):
    flags = 0
    comment = ''
    help = ''
    wanted_name = 'AlleyCat'
    wanted_hotkey = ''
    menu_name = 'View/Graphs/'
    menu_context = []

    to_from_action_name = 'tocurrfrom:action'
    from_to_action_name = 'fromcurrto:action'
    curr_func_curr_block_action_name = 'currfunccurrblock:action'

    to_from_menu_name = 'Find paths to the current function from...'
    from_to_menu_name = 'Find paths from the current function to...'
    curr_func_curr_block_menu_name = 'Find paths in the current function to ' \
                                     'the current code block'

    def init(self):
        if idaapi.IDA_SDK_VERSION >= 700:
            # Add ALLEYCAT_LIMIT variable to the global namespace so it can be
            # accessed from the IDA python terminal.
            global ALLEYCAT_LIMIT
            add_to_namespace(
                '__main__', 'alleycat', 'ALLEYCAT_LIMIT', ALLEYCAT_LIMIT)

            # Add functions to global namespace.
            add_to_namespace(
                '__main__', 'alleycat', 'AlleyCatFunctionPaths',
                AlleyCatFunctionPaths)
            add_to_namespace(
                '__main__', 'alleycat', 'AlleyCatCodePaths', AlleyCatCodePaths)
            add_to_namespace(
                '__main__', 'alleycat', 'AlleyCatGraph', AlleyCatGraph)

            to_curr_from_desc = idaapi.action_desc_t(
                self.to_from_action_name, self.to_from_menu_name,
                ToCurrentFromAction(), self.wanted_hotkey,
                'Find paths to the current function from...', 199)

            from_curr_to_desc = idaapi.action_desc_t(
                self.from_to_action_name, self.from_to_menu_name,
                FromCurrentToAction(), self.wanted_hotkey,
                'Find paths from the current function to...', 199)

            curr_func_to_block_desc = idaapi.action_desc_t(
                self.curr_func_curr_block_action_name,
                self.curr_func_curr_block_menu_name,
                InCurrentFunctionToCurrentCodeBlockAction(),
                self.wanted_hotkey,
                'Find paths in the current function to the current code block',
                199)

            idaapi.register_action(to_curr_from_desc)
            idaapi.register_action(from_curr_to_desc)
            idaapi.register_action(curr_func_to_block_desc)

            idaapi.attach_action_to_menu(
                self.menu_name, self.to_from_action_name, idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu(
                self.menu_name, self.from_to_action_name, idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu(
                self.menu_name, self.curr_func_curr_block_action_name,
                idaapi.SETMENU_APP)

        else:
            self.menu_context.append(
                idaapi.add_menu_item(
                    self.menu_name, self.to_from_menu_name, "", 0,
                    find_paths_from_many, (None,)))

            self.menu_context.append(
                idaapi.add_menu_item(
                    self.menu_name, self.from_to_menu_name, "", 0,
                    find_paths_to_many, (None,)))

            self.menu_context.append(
                idaapi.add_menu_item(
                    self.menu_name, self.curr_func_curr_block_menu_name, "", 0,
                    find_paths_to_code_block, (None,)))

        return idaapi.PLUGIN_KEEP

    def term(self):
        if idaapi.IDA_SDK_VERSION >= 700:
            idaapi.detach_action_from_menu(
                self.menu_name, self.to_from_action_name)
            idaapi.detach_action_from_menu(
                self.menu_name, self.from_to_action_name)
            idaapi.detach_action_from_menu(
                self.menu_name, self.curr_func_curr_block_action_name)
        else:
            for context in self.menu_context:
                idaapi.del_menu_item(context)
        return None

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return idapathfinder_t()


repository: assafcarlsbad/brick
file: deps/alleycat.py
wanted_name: AlleyCat
created_at: 2021-05-19 07:44:44+00:00
pushed_at: 2022-03-12 18:26:31+00:00
url:
https://github.com/assafcarlsbad/brick/blob/f9f6183aa337d9da0375b339c6f6064eea1de995/deps/alleycat.py

content:
import sys
import idc
import time
import idaapi
import idautils

from shims import ida_shims

# This limits the depth of any individual path, as well as the maximum
# number of paths that will be searched; this is needed for practical
# reasons, as IDBs with tens of thousands of functions take a long time
# to exhaust all possible paths without some practical limitation.
#
# This is global so it's easy to change from the IDAPython prompt.
ALLEYCAT_LIMIT = 10000


def add_to_namespace(namespace, source, name, variable):
    '''
    Add a variable to a different namespace, likely __main__.
    '''
    import importlib

    importer_module = sys.modules[namespace]
    if source in list(sys.modules.keys()):
        if not (sys.version_info.major == 3 and sys.version_info.minor >= 4):
            import imp
            imp.reload(sys.modules[source])
        else:
            importlib.reload(sys.modules[source])
    else:
        m = importlib.import_module(source, None)
        sys.modules[source] = m

    setattr(importer_module, name, variable)


class AlleyCatException(Exception):
    pass


class AlleyCat(object):
    '''
    Class which resolves code paths. This is where most of the work is done.
    '''

    def __init__(self, start, end, quiet=False):
        '''
        Class constructor.

        @start - The start address.
        @end   - The end address.

        Returns None.
        '''
        global ALLEYCAT_LIMIT
        self.limit = ALLEYCAT_LIMIT
        self.paths = []
        self.quiet = quiet

        # We work backwards via xrefs, so we start at the end and end at the
        # start
        if not self.quiet:
            print("Generating call paths from %s to %s..." % (self._name(end),
                                                              self._name(start)))
        self._build_paths(start, end)

    def _name(self, ea):
        name = ida_shims.get_name(ea)
        if not name:
            name = ida_shims.get_func_off_str(ea)
            if not name:
                name = '0x%X' % ea
        return name

    def _add_path(self, path):
        if path not in self.paths:
            self.paths.append(path)

    def _build_paths(self, start, end=idc.BADADDR):
        partial_paths = [[start]]

        # Loop while there are still unresolve paths and while all path sizes
        # have not exceeded ALLEYCAT_LIMIT
        while partial_paths and \
                len(self.paths) < self.limit and \
                len(partial_paths) < self.limit:
            callers = set()

            # Callee is the last entry of the first path in partial paths.
            # The first path list will change as paths are completed and
            # popped from the list.
            callee = partial_paths[0][-1]

            # Find all unique functions that reference the callee, assuming this
            # path has not exceeded ALLEYCAT_LIMIT.
            if len(partial_paths[0]) < self.limit:
                for xref in idautils.XrefsTo(callee):
                    caller = self._get_code_block(xref.frm)
                    if caller:
                        start_ea = ida_shims.start_ea(caller)

                        if start_ea not in callers:
                            callers.add(start_ea)

            # If there are callers to the callee, remove the callee's current
            # path and insert new ones with the new callers appended.
            if callers:
                base_path = partial_paths.pop(0)
                for caller in callers:

                    # Don't want to loop back on ourselves in the same path
                    if caller in base_path:
                        continue

                    # If we've reached the desired end node, don't go any
                    # further down this path
                    if caller == end:
                        self._add_path((base_path + [caller])[::-1])
                    else:
                        partial_paths.append(base_path + [caller])
            # Else, our end node is not in this path, so don't include it in the
            # finished path list.
            elif end not in partial_paths[0]:
                partial_paths.pop(0)
            # If there were no callers then this path has been exhausted and
            # should be popped from the partial path list into the finished path list.
            elif end in partial_paths[0]:
                # Paths start with the end function and end with the start
                # function; reverse it.
                self._add_path(partial_paths.pop(0)[::-1])

    def _get_code_block(self, ea):
        return idaapi.get_func(ea)


class AlleyCatFunctionPaths(AlleyCat):

    def __init__(self, start_ea, end_ea, quiet=False):

        # We work backwards via xrefs, so we start at the end and end at the start
        try:
            func = idaapi.get_func(end_ea)
            start = ida_shims.start_ea(func)
        except:
            raise AlleyCatException("Address 0x%X is not part of a function!" %
                                    end_ea)
        try:
            func = idaapi.get_func(start_ea)
            end = ida_shims.start_ea(func)
        except:
            end = idc.BADADDR

        super(AlleyCatFunctionPaths, self).__init__(start, end, quiet)


class AlleyCatCodePaths(AlleyCat):

    def __init__(self, start_ea, end_ea, quiet=False):
        end_func = idaapi.get_func(end_ea)
        start_func = idaapi.get_func(start_ea)

        if not start_func:
            raise AlleyCatException("Address 0x%X is not part of a function!" %
                                    start_ea)
        if not end_func:
            raise AlleyCatException("Address 0x%X is not part of a function!" %
                                    end_ea)

        start_func_ea = ida_shims.start_ea(start_func)
        end_func_ea = ida_shims.start_ea(end_func)

        if start_func_ea != end_func_ea:
            raise AlleyCatException("The start and end addresses are not part "
                                    "of the same function!")

        self.func = start_func
        self.blocks = [block for block in idaapi.FlowChart(self.func)]

        # We work backwards via xrefs, so we start at the end and end at the start
        end_block = self._get_code_block(start_ea)
        start_block = self._get_code_block(end_ea)

        if not end_block:
            raise AlleyCatException("Failed to find the code block associated "
                                    "with address 0x%X" % start_ea)
        if not start_block:
            raise AlleyCatException("Failed to find the code block associated "
                                    "with address 0x%X" % end_ea)

        start_block_ea = ida_shims.start_ea(start_block)
        end_block_ea = ida_shims.start_ea(end_block)

        super(AlleyCatCodePaths, self).__init__(
            start_block_ea, end_block_ea, quiet)

    def _get_code_block(self, ea):
        for block in self.blocks:
            start_ea = ida_shims.start_ea(block)
            end_ea = ida_shims.end_ea(block)
            if start_ea <= ea and end_ea > ea:
                return block
        return None


# Everything below here is just IDA UI/Plugin stuff


class AlleyCatGraphHistory(object):
    '''
    Manages include/exclude graph history.
    '''

    INCLUDE_ACTION = 0
    EXCLUDE_ACTION = 1

    def __init__(self):
        self.history = []
        self.includes = []
        self.excludes = []
        self.history_index = 0
        self.include_index = 0
        self.exclude_index = 0

    def reset(self):
        self.history = []
        self.includes = []
        self.excludes = []
        self.history_index = 0
        self.include_index = 0
        self.exclude_index = 0

    def update_history(self, action):
        if self.excludes and len(self.history)-1 != self.history_index:
            self.history = self.history[0:self.history_index+1]
        self.history.append(action)
        self.history_index = len(self.history)-1

    def add_include(self, obj):
        if self.includes and len(self.includes)-1 != self.include_index:
            self.includes = self.includes[0:self.include_index+1]
        self.includes.append(obj)
        self.include_index = len(self.includes)-1
        self.update_history(self.INCLUDE_ACTION)

    def add_exclude(self, obj):
        if len(self.excludes)-1 != self.exclude_index:
            self.excludes = self.excludes[0:self.exclude_index+1]
        self.excludes.append(obj)
        self.exclude_index  = len(self.excludes)-1
        self.update_history(self.EXCLUDE_ACTION)

    def get_includes(self):
        return set(self.includes[0:self.include_index+1])

    def get_excludes(self):
        return set(self.excludes[0:self.exclude_index+1])

    def undo(self):
        if self.history:
            if self.history[self.history_index] == self.INCLUDE_ACTION:
                if self.include_index >= 0:
                    self.include_index -= 1
            elif self.history[self.history_index] == self.EXCLUDE_ACTION:
                if self.exclude_index >= 0:
                    self.exclude_index -= 1

            self.history_index -= 1
            if self.history_index < 0:
                self.history_index = 0

    def redo(self):
        self.history_index += 1
        if self.history_index >= len(self.history):
            self.history_index = len(self.history)-1

        if self.history[self.history_index] == self.INCLUDE_ACTION:
            if self.include_index < len(self.includes)-1:
                self.include_index += 1
        elif self.history[self.history_index] == self.EXCLUDE_ACTION:
            if self.exclude_index < len(self.excludes)-1:
                self.exclude_index += 1


class AlleyCatGraph(idaapi.GraphViewer):
    '''
    Displays the graph and manages graph actions.
    '''
    def __init__(self, results, title="AlleyCat Graph"):
        idaapi.GraphViewer.__init__(self, title)
        self.results = results

        self.nodes_ea2id = {}
        self.nodes_id2ea = {}
        self.edges = {}
        self.end_nodes = []
        self.edge_nodes = []
        self.start_nodes = []

        self.history = AlleyCatGraphHistory()
        self.include_on_click = False
        self.exclude_on_click = False

        self.cmd_undo = None
        self.cmd_redo = None
        self.cmd_reset = None
        self.cmd_exclude = None
        self.cmd_include = None
        self.cmd_unhighlight = None

    def Show(self):
        '''
        Display the graph.

        Returns True on success, False on failure.
        '''
        if not idaapi.GraphViewer.Show(self):
            return False
        else:
            self.cmd_undo = self.AddCommand("Undo", "")
            self.cmd_redo = self.AddCommand("Redo", "")
            self.cmd_reset = self.AddCommand("Reset graph", "")
            self.cmd_exclude = self.AddCommand("Exclude node", "")
            self.cmd_include = self.AddCommand("Include node", "")
            self.cmd_unhighlight = self.AddCommand(
                "Temporarily un-highlight all paths", "")
            return True

    def OnRefresh(self):
        # Clear the graph before refreshing
        self.clear()
        self.nodes_ea2id = {}
        self.nodes_id2ea = {}
        self.edges = {}
        self.end_nodes = []
        self.edge_nodes = []
        self.start_nodes = []

        includes = self.history.get_includes()
        excludes = self.history.get_excludes()

        for path in self.results:
            parent_node = None

            # Check to see if this path contains all nodes marked for explicit
            # inclusion
            if (set(path) & includes) != includes:
                continue

            # Check to see if this path contains any nodes marked for explicit
            # exclusion
            if (set(path) & excludes) != set():
                continue

            for ea in path:
                # If this node already exists, use its existing node ID
                if ea in self.nodes_ea2id:
                    this_node = self.nodes_ea2id[ea]
                # Else, add this node to the graph
                else:
                    this_node = self.AddNode(self.get_name_by_ea(ea))
                    self.nodes_ea2id[ea] = this_node
                    self.nodes_id2ea[this_node] = ea

                # If there is a parent node, add an edge between the parent node
                # and this one
                if parent_node is not None:
                    self.AddEdge(parent_node, this_node)
                    if this_node not in self.edges[parent_node]:
                        self.edges[parent_node].append(this_node)

                # Update the parent node for the next loop
                parent_node = this_node
                if parent_node not in self.edges:
                    self.edges[parent_node] = []

                # Highlight this node in the disassembly window
                self.highlight(ea)

            try:
                # Track the first, last, and next to last nodes in each path for
                # proper colorization in self.OnGetText.
                self.start_nodes.append(self.nodes_ea2id[path[0]])
                self.end_nodes.append(self.nodes_ea2id[path[-1]])
                self.edge_nodes.append(self.nodes_ea2id[path[-2]])
            except:
                pass

        return True

    def OnGetText(self, node_id):
        color = idc.DEFCOLOR

        if node_id in self.edge_nodes:
            color = 0x00ffff
        elif node_id in self.start_nodes:
            color = 0x00ff00
        elif node_id in self.end_nodes:
            color = 0x0000ff

        return self[node_id], color

    def OnHint(self, node_id):
        hint = ""

        try:
            for edge_node in self.edges[node_id]:
                hint += "%s\n" % self[edge_node]
        except:
            pass

        return hint

    def OnCommand(self, cmd_id):
        if self.cmd_undo == cmd_id:
            if self.include_on_click or self.exclude_on_click:
                self.include_on_click = False
                self.exclude_on_click = False
            else:
                self.history.undo()
            self.Refresh()
        elif self.cmd_redo == cmd_id:
            self.history.redo()
            self.Refresh()
        elif self.cmd_include == cmd_id:
            self.include_on_click = True
        elif self.cmd_exclude == cmd_id:
            self.exclude_on_click = True
        elif self.cmd_reset == cmd_id:
            self.include_on_click = False
            self.exclude_on_click = False
            self.history.reset()
            self.Refresh()
        elif self.cmd_unhighlight == cmd_id:
            self.unhighlight_all()

    def OnClick(self, node_id):
        if self.include_on_click:
            self.history.add_include(self.nodes_id2ea[node_id])
            self.include_on_click = False
        elif self.exclude_on_click:
            self.history.add_exclude(self.nodes_id2ea[node_id])
            self.exclude_on_click = False
        self.Refresh()

    def OnDblClick(self, node_id):
        xref_locations = []
        node_ea = self.get_ea_by_name(self[node_id])

        if node_id in self.edges:
            for edge_node_id in self.edges[node_id]:

                edge_node_name = self[edge_node_id]
                edge_node_ea = self.get_ea_by_name(edge_node_name)

                if edge_node_ea != idc.BADADDR:
                    for xref in idautils.XrefsTo(edge_node_ea):
                        # Is the specified node_id the source of this xref?
                        if self.match_xref_source(xref, node_ea):
                            xref_locations.append((xref.frm, edge_node_ea))

        if xref_locations:
            xref_locations.sort()

            print("")
            print("Path Xrefs from %s:" % self[node_id])
            print("-" * 100)
            for (xref_ea, dst_ea) in xref_locations:
                print("%-50s  =>  %s" % (self.get_name_by_ea(xref_ea),
                                         self.get_name_by_ea(dst_ea)))
            print("-" * 100)
            print("")

            ida_shims.jumpto(xref_locations[0][0])
        else:
            ida_shims.jumpto(node_ea)

    def OnClose(self):
        if ida_shims.ask_yn(1, "Path nodes have been highlighted in the "
                               "disassembly window. Undo highlighting?") == 1:
            self.unhighlight_all()

    def match_xref_source(self, xref, source):
        return ((xref.type != idc.fl_F) and
                (ida_shims.get_func_attr(xref.frm, idc.FUNCATTR_START) == source))

    def get_ea_by_name(self, name):
        '''
        Get the address of a location by name.

        @name - Location name

        Returns the address of the named location, or idc.BADADDR on failure.
        '''
        # This allows support of the function offset style names (e.g., main+0C)
        ea = 0
        if '+' in name:
            (func_name, offset) = name.split('+')
            base_ea = ida_shims.get_name_ea_simple(func_name)
            if base_ea != idc.BADADDR:
                try:
                    ea = base_ea + int(offset, 16)
                except:
                    ea = idc.BADADDR
        else:
            ea = ida_shims.get_name_ea_simple(name)
            if ea == idc.BADADDR:
                try:
                    ea = int(name, 0)
                except:
                    ea = idc.BADADDR

        return ea

    def clear(self):
        # Clears the graph and unhighlights the disassembly
        self.Clear()
        self.unhighlight_all()

    def get_name_by_ea(self, ea):
        '''
        Get the name of the specified address.

        @ea - Address.

        Returns a name for the address, one of idc.Name, idc.GetFuncOffset or
        0xXXXXXXXX.
        '''
        name = ida_shims.get_name(ea)
        if not name:
            name = ida_shims.get_func_off_str(ea)
            if not name:
                name = "0x%X" % ea
        return name

    def colorize_node(self, ea, color):
        # Colorizes an entire code block
        func = idaapi.get_func(ea)
        if func:
            for block in idaapi.FlowChart(func):
                block_start_ea = ida_shims.start_ea(block)
                block_end_ea = ida_shims.end_ea(block)

                if block_start_ea <= ea and block_end_ea > ea:
                    ea = block_start_ea
                    while ea < block_end_ea:
                        idaapi.set_item_color(ea, color)
                        ea = ida_shims.next_head(ea)
                    break

    def highlight(self, ea):
        # Highlights an entire code block
        self.colorize_node(ea, 0x00FF00)

    def unhighlight(self, ea):
        # Unhighlights an entire code block
        self.colorize_node(ea, idc.DEFCOLOR)

    def unhighlight_all(self):
        # Unhighlights all code blocks
        for path in self.results:
            for ea in path:
                self.unhighlight(ea)


class AlleyCatPaths(object):
    def _current_function(self):
        function = idaapi.get_func(ida_shims.get_screen_ea())
        return ida_shims.start_ea(function)

    def _find_and_plot_paths(self, sources, targets, klass=AlleyCatFunctionPaths):
        results = []

        for target in targets:
            for source in sources:
                s = time.time()
                r = klass(source, target).paths
                e = time.time()
                print("Found %d paths in %f seconds." % (len(r), (e-s)))

                if r:
                    results += r
                else:
                    name = ida_shims.get_name(target)
                    if not name:
                        name = "0x%X" % target
                    print("No paths found to", name)

        if results:
            # Be sure to close any previous graph before creating a new one.
            # Failure to do so may crash IDA.
            try:
                self.graph.Close()
            except:
                pass

            self.graph = AlleyCatGraph(results, 'Path Graph')
            self.graph.Show()

    def _get_user_selected_functions(self, many=False):
        functions = []
        ea = ida_shims.get_screen_ea()
        try:
            current_function = ida_shims.get_func_attr(ea, idc.FUNCATTR_START)
        except:
            current_function = None

        while True:
            function = ida_shims.choose_func(
                "Select a function and click 'OK' until all functions have "
                "been selected. When finished, click 'Cancel' to display the "
                "graph.")

            if ida_shims.get_screen_ea() != ea:
                ida_shims.jumpto(ea)

            if not function or \
                    function == idc.BADADDR or function == current_function:
                break
            elif function not in functions:
                functions.append(function)

            if not many:
                break

        return functions

    def FindPathsToCodeBlock(self):
        target = ida_shims.get_screen_ea()
        source = self._current_function()

        if source:
            self._find_and_plot_paths(
                [source], [target], klass=AlleyCatCodePaths)

    def FindPathsToMany(self):
        source = self._current_function()

        if source:
            targets = self._get_user_selected_functions(many=True)
            if targets:
                self._find_and_plot_paths([source], targets)

    def FindPathsFromMany(self):
        target = self._current_function()

        if target:
            sources = self._get_user_selected_functions(many=True)
            if sources:
                self._find_and_plot_paths(sources, [target])


# Helper functions to execute commands selected from dropdown menus.
# args parameter is required for IDA version < 7.0
def find_paths_from_many(arg=None):
    AlleyCatPaths().FindPathsFromMany()


def find_paths_to_many(arg=None):
    AlleyCatPaths().FindPathsToMany()


def find_paths_to_code_block(args=None):
    AlleyCatPaths().FindPathsToCodeBlock()


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

        def activate(self, ctx):
            find_paths_from_many()
            return 1

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


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

        def activate(self, ctx):
            find_paths_to_many()
            return 1

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

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

        def activate(self, ctx):
            find_paths_to_code_block()
            return 1

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


class idapathfinder_t(idaapi.plugin_t):
    flags = 0
    comment = ''
    help = ''
    wanted_name = 'AlleyCat'
    wanted_hotkey = ''
    menu_name = 'View/Graphs/'
    menu_context = []

    to_from_action_name = 'tocurrfrom:action'
    from_to_action_name = 'fromcurrto:action'
    curr_func_curr_block_action_name = 'currfunccurrblock:action'

    to_from_menu_name = 'Find paths to the current function from...'
    from_to_menu_name = 'Find paths from the current function to...'
    curr_func_curr_block_menu_name = 'Find paths in the current function to ' \
                                     'the current code block'

    def init(self):
        if idaapi.IDA_SDK_VERSION >= 700:
            # Add ALLEYCAT_LIMIT variable to the global namespace so it can be
            # accessed from the IDA python terminal.
            global ALLEYCAT_LIMIT
            add_to_namespace(
                '__main__', 'alleycat', 'ALLEYCAT_LIMIT', ALLEYCAT_LIMIT)

            # Add functions to global namespace.
            add_to_namespace(
                '__main__', 'alleycat', 'AlleyCatFunctionPaths',
                AlleyCatFunctionPaths)
            add_to_namespace(
                '__main__', 'alleycat', 'AlleyCatCodePaths', AlleyCatCodePaths)
            add_to_namespace(
                '__main__', 'alleycat', 'AlleyCatGraph', AlleyCatGraph)

            to_curr_from_desc = idaapi.action_desc_t(
                self.to_from_action_name, self.to_from_menu_name,
                ToCurrentFromAction(), self.wanted_hotkey,
                'Find paths to the current function from...', 199)

            from_curr_to_desc = idaapi.action_desc_t(
                self.from_to_action_name, self.from_to_menu_name,
                FromCurrentToAction(), self.wanted_hotkey,
                'Find paths from the current function to...', 199)

            curr_func_to_block_desc = idaapi.action_desc_t(
                self.curr_func_curr_block_action_name,
                self.curr_func_curr_block_menu_name,
                InCurrentFunctionToCurrentCodeBlockAction(),
                self.wanted_hotkey,
                'Find paths in the current function to the current code block',
                199)

            idaapi.register_action(to_curr_from_desc)
            idaapi.register_action(from_curr_to_desc)
            idaapi.register_action(curr_func_to_block_desc)

            idaapi.attach_action_to_menu(
                self.menu_name, self.to_from_action_name, idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu(
                self.menu_name, self.from_to_action_name, idaapi.SETMENU_APP)
            idaapi.attach_action_to_menu(
                self.menu_name, self.curr_func_curr_block_action_name,
                idaapi.SETMENU_APP)

        else:
            self.menu_context.append(
                idaapi.add_menu_item(
                    self.menu_name, self.to_from_menu_name, "", 0,
                    find_paths_from_many, (None,)))

            self.menu_context.append(
                idaapi.add_menu_item(
                    self.menu_name, self.from_to_menu_name, "", 0,
                    find_paths_to_many, (None,)))

            self.menu_context.append(
                idaapi.add_menu_item(
                    self.menu_name, self.curr_func_curr_block_menu_name, "", 0,
                    find_paths_to_code_block, (None,)))

        return idaapi.PLUGIN_KEEP

    def term(self):
        if idaapi.IDA_SDK_VERSION >= 700:
            idaapi.detach_action_from_menu(
                self.menu_name, self.to_from_action_name)
            idaapi.detach_action_from_menu(
                self.menu_name, self.from_to_action_name)
            idaapi.detach_action_from_menu(
                self.menu_name, self.curr_func_curr_block_action_name)
        else:
            for context in self.menu_context:
                idaapi.del_menu_item(context)
        return None

    def run(self, arg):
        pass


def PLUGIN_ENTRY():
    return idapathfinder_t()


repository: bubbadestroy/binwalk
file: src/scripts/binida.py
description: updated dep.sh for focal ubuntu
wanted_name: Binwalk IDA Plugin
comment: Scan the current IDB for file signatures
created_at: 2020-11-27 04:39:59+00:00
pushed_at: 2020-11-27 04:43:51+00:00
url:
https://github.com/bubbadestroy/binwalk/blob/ffcf04ee16d6954038645a4d3a99a5468bfa7ab6/src/scripts/binida.py

content:
if idaapi.IDA_SDK_VERSION <= 695:
    import idc
    import idaapi
    import binwalk
if idaapi.IDA_SDK_VERSION >= 700:
    import ida_idc
    import ida_idaapi
    import binwalk

    from idaapi import *
else:
    pass

# use 'try' here for compatibility with older API
# use Actions API for handlers
try:
    class OpHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)

        def activate(self, ctx):
            arg = None
            a = binwalk_t()
            a.opcode_scan(arg)
            return 1

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

# use 'try' here for compatibility with older API
# use Actions API for handlers
try:
    class SigHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)

        def activate(self, ctx):
            arg = None
            b = binwalk_t()
            b.signature_scan(arg)
            return 1

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

class binwalk_t(idaapi.plugin_t):
    flags = 0
    comment = "Scan the current IDB for file signatures"
    help = ""
    wanted_name = "Binwalk IDA Plugin"
    wanted_hotkey = ""

    def init(self):
        if idaapi.IDA_SDK_VERSION <= 695:
            self.menu_context_1 = idaapi.add_menu_item(
                "Search/", "binwalk opcodes", "", 0, self.opcode_scan, (None,))
            self.menu_context_2 = idaapi.add_menu_item(
                "Search/", "binwalk signatures", "", 0, self.signature_scan, (None,))

        if idaapi.IDA_SDK_VERSION >= 700:
            # populate action menus
            action_desc = idaapi.action_desc_t(
                'my:opaction',  # action name. This acts like an ID and must be unique
                'Binwalk opcodes',  # text for this action
                OpHandler(),  # the action handler
                '',  # optional shortcut key
                'Binwalk opcodes',  # optional action tooltip for menus/toolbar
                )

            # Register the action
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(
                'Search/',
                'my:opaction',
                idaapi.SETMENU_APP)

            # populate action menus
            action_desc = idaapi.action_desc_t(
                'my:sigaction',
                'Binwalk signatures',
                SigHandler(),
                '',
                'Binwalk signatures',
                )

            # Register the action
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(
                'Search/',
                'my:sigaction',
                idaapi.SETMENU_APP)

        else:
            pass

        return idaapi.PLUGIN_KEEP

    def term(self):
        if idaapi.IDA_SDK_VERSION <= 695:
            idaapi.del_menu_item(self.menu_context_1)
            idaapi.del_menu_item(self.menu_context_2)
        if idaapi.IDA_SDK_VERSION >= 700:
            idaapi.detach_action_from_menu(
                'Search/',
                'my:opaction')
            idaapi.detach_action_from_menu(
                'Search/',
                'my:sigaction')
        else:
            pass

        return None

    def run(self, arg):
        return None

    def signature_scan(self, arg):
        binwalk.scan(idc.GetIdbPath(), signature=True)

    def opcode_scan(self, arg):
        binwalk.scan(idc.GetIdbPath(), opcode=True)


def PLUGIN_ENTRY():
    return binwalk_t()


repository: xuhe2/python_course_design
file: binwalk-2.3.2/src/scripts/binida.py
description: ZSTU 浙江理工大学 PYTHON 大作业
wanted_name: Binwalk IDA Plugin
comment: Scan the current IDB for file signatures
created_at: 2024-01-13 05:02:01+00:00
pushed_at: 2024-01-15 01:33:25+00:00
url:
https://github.com/xuhe2/python_course_design/blob/c003e4f6da6633d16b67e2025fd16c85eeba6e77/binwalk-2.3.2/src/scripts/binida.py

content:
if idaapi.IDA_SDK_VERSION <= 695:
    import idc
    import idaapi
    import binwalk
if idaapi.IDA_SDK_VERSION >= 700:
    import ida_idc
    import ida_idaapi
    import binwalk

    from idaapi import *
else:
    pass

# use 'try' here for compatibility with older API
# use Actions API for handlers
try:
    class OpHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)

        def activate(self, ctx):
            arg = None
            a = binwalk_t()
            a.opcode_scan(arg)
            return 1

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

# use 'try' here for compatibility with older API
# use Actions API for handlers
try:
    class SigHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)

        def activate(self, ctx):
            arg = None
            b = binwalk_t()
            b.signature_scan(arg)
            return 1

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

class binwalk_t(idaapi.plugin_t):
    flags = 0
    comment = "Scan the current IDB for file signatures"
    help = ""
    wanted_name = "Binwalk IDA Plugin"
    wanted_hotkey = ""

    def init(self):
        if idaapi.IDA_SDK_VERSION <= 695:
            self.menu_context_1 = idaapi.add_menu_item(
                "Search/", "binwalk opcodes", "", 0, self.opcode_scan, (None,))
            self.menu_context_2 = idaapi.add_menu_item(
                "Search/", "binwalk signatures", "", 0, self.signature_scan, (None,))

        if idaapi.IDA_SDK_VERSION >= 700:
            # populate action menus
            action_desc = idaapi.action_desc_t(
                'my:opaction',  # action name. This acts like an ID and must be unique
                'Binwalk opcodes',  # text for this action
                OpHandler(),  # the action handler
                '',  # optional shortcut key
                'Binwalk opcodes',  # optional action tooltip for menus/toolbar
                )

            # Register the action
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(
                'Search/',
                'my:opaction',
                idaapi.SETMENU_APP)

            # populate action menus
            action_desc = idaapi.action_desc_t(
                'my:sigaction',
                'Binwalk signatures',
                SigHandler(),
                '',
                'Binwalk signatures',
                )

            # Register the action
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(
                'Search/',
                'my:sigaction',
                idaapi.SETMENU_APP)

        else:
            pass

        return idaapi.PLUGIN_KEEP

    def term(self):
        if idaapi.IDA_SDK_VERSION <= 695:
            idaapi.del_menu_item(self.menu_context_1)
            idaapi.del_menu_item(self.menu_context_2)
        if idaapi.IDA_SDK_VERSION >= 700:
            idaapi.detach_action_from_menu(
                'Search/',
                'my:opaction')
            idaapi.detach_action_from_menu(
                'Search/',
                'my:sigaction')
        else:
            pass

        return None

    def run(self, arg):
        return None

    def signature_scan(self, arg):
        binwalk.scan(idc.GetIdbPath(), signature=True)

    def opcode_scan(self, arg):
        binwalk.scan(idc.GetIdbPath(), opcode=True)


def PLUGIN_ENTRY():
    return binwalk_t()


repository: TheRealMilesLee/Random-ideas
file: 路由器固件相关/src/scripts/binida.py
description: The most random ideas coming from my awesome brain
wanted_name: Binwalk IDA Plugin
comment: Scan the current IDB for file signatures
created_at: 2023-12-30 23:48:58+00:00
pushed_at: 2025-05-12 07:37:25+00:00
url:
https://github.com/TheRealMilesLee/Random-ideas/blob/ce152657bd12945ccc4782d860b58f9cd4818710/%E8%B7%AF%E7%94%B1%E5%99%A8%E5%9B%BA%E4%BB%B6%E7%9B%B8%E5%85%B3/src/scripts/binida.py


repository: sungchi127/NYCU-Secure-Programming-2022
file: 程安/reverse1/trace/binwalk-master/src/scripts/binida.py
wanted_name: Binwalk IDA Plugin
comment: Scan the current IDB for file signatures
created_at: 2025-04-04 00:56:01+00:00
pushed_at: 2025-04-04 01:04:21+00:00
url:
https://github.com/sungchi127/NYCU-Secure-Programming-2022/blob/4704caf0e994344bf9495433519de15c434ad673/%E7%A8%8B%E5%AE%89/reverse1/trace/binwalk-master/src/scripts/binida.py


repository: zengfr/romhack
file: ida/protobuf-finder.py
description: rom hack for Arcade mame mess sfc snes sega md neogeo romhacking
wanted_name: Protobuf
comment: Protobuf string finder.
created_at: 2019-12-20 02:17:04+00:00
pushed_at: 2024-09-24 06:42:00+00:00
url:
https://github.com/zengfr/romhack/blob/adf6412c2a969486918bb00c18a2c989abdeaad5/ida/protobuf-finder.py

content:
"""
Protobuf Finder
 > IDA plugin for reconstructing original .proto files from binary
"""

import re

# require google's protobuf library
from google.protobuf.descriptor_pb2 import FileDescriptorProto, FieldDescriptorProto
from google.protobuf.message import DecodeError

# IDA API imports
import idaapi
import ida_bytes
import ida_ida
import ida_kernwin


# Max protobuf size - used to load code from IDA database; 
MAX_PROTOBUF_SIZE = 0x100000

# Icon
icon = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x000\x00\x00\x00%\x08\x06\x00\x00\x00\x04\x19j\xaf\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\x0b\xfca\x05\x00\x00\x00\tpHYs\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7o\xa8d\x00\x00\x06\x0cIDATXG\xed\x97{PTu\x14\xc7\xcf\xdd]vY\x9e\xbb\x08\x08\xb8\x12h>\x92t@\x13\x07\xb2\x04\x93\xac\x1c\tL\x19\x9f\x199\x16\x16\x1a\x98\xa3i9\x84J\xa33\x068\xcd\xa0\xd9\x03m0Lj4\x07\xa3\xc9\x07\xa4#\xf8\xd8\x89-5\x1b\x9f\xa4;$\x12\xb2\xbca\x1f\xf7\xf6\xfb\xdd=\xbb\xee\xb2w/\xeb\x8c\x7f\xf4\xc7~\x98\xe3\xfd}\xcf]\x99s\xf8\x9d\xf3\xfb\x9d\x05/^\xbcx\xd1\x10+&v\x97\x18\xe7`\xf5\xc4\x0e\x11[@\xec\x7f\t\r\x9c\x06\xe8\x18\xb4;\xa3\x9f\xa3\x9f\x7f\xec0\xf8\xf4\x98\xbb\xebr\x17W^\xb9\x9a\xb8\xbe\xe6\xe4{\xe8\xf2\x98\xc9k2v\x8dz%\xa9\x89\x05\x0b\xaf\x1f\xf4wv\x87\xfa\xaa\x03\xa8\xee6\xf7\x0f<\x19\x1ce({.\xa7\x92\x7f\xe9!\x8f\x94\x80~c^^C]]\xc9\x82\x06\x1dz\xac\xbc<<\x04\xb2\xa3#!\xd2W\x0e\xe1\xea\x90\xf6\xf0\xf9\x0b\xa7\xa8__\xf9\x0cyE\xcb\xc7\xa9\x84\x026\xcd\x02\xe9\xe8PT\xce0\xe4gz\xd8\x84\xcc\xea\xf4\xcdG\xd05$\x1e\'\xa0/)\x8a\xef\xff\xa5\xa6v\xfa\xa9\x0b\xaa{\x03F\xde\x17\xa1\x90CUb\x1c\xff\xa4H\xfc\x03\xa0S\xa1\xccL8T\xed\x18@\x121{\tI\xd4~\x10\xb83\x9d.\x05\x912\x12CV\xec\xd4\x84=\xa9\xf9M\xe8\x12E\x82OQn\x97\x14\xa8\x06\xce5|\xf5\x93\xbe\xc5\x1e<\xc51x\x8a1X\xb5sP\xf0\x94\x06b4\t\x1e\xb6\xbd\x17LZ\xda\xef\xc2X8Vu\xb6\xe5F-\xca!\xf1(\x01\xd0\xea\n\xd9\xb6\xd6\xc95\xf7\xdb\xd0\x01\xb0i\xec\x13N\xc1K\xa2c\xb4\x11\xd9\xb9E(\x07\xa3\'\x96e]\x02\x98\xab\xafv\xe1R\x90;=m1s\x8fm9\x80R\x94!\x13\xd8\xf1\xf5\xad\\CG\xcf\x1a\x96\x9c%5-\x0f\xd0\x0b\xa4\xe6#p\x05`\xf2\x0b0\x04\xcdxa\x89:5\xd5\x80.!\xe8N\xf0\x18\xf5\xed]a\x8a\xc0n\x94\x82\x9ci\xb9\xba\xf8\xc5\xa3\x1fe\xa0t\x8bh\x02{k\xbb\xe3O\xb5\x86o\xdd3~\x13\x18X\x16\xbd\xce0r\x85\xd1\x1c\xa4\xca\x0e[\xfe\xd65t\xb9\x83\xee\x82\x15\x8e\x8b\xf2\xed\xe3\xe6\x92\':\\\xe1\xc8;\xed\xbf\xb7\xcasjw\xc7\xa0K\x10\xb7\t\x94\x1cnW\x9d\xbc\xc4\xec5\x99Au]\x9d\x08gF\xa6C\xb8C\xc9\xd8z\xc1<,\xb4lR\xc5\x0f\x1e\x9f\x1a6.\xaf\xfc\xb2n\x822r\x17JAh?\x9ch\xd6\x1dE)\x88\xdb\x04\xb4\xff\xf8\x14\xb4vqSQ\xc2\xcf\xe3\xf3!\xc8?\x18\x15@\xa3\xa1\x1b\x98(M\xfd\xf0\xb5\x9b\x0b\xd15\x14\xf9\xf8\xa4T\xd1\x7f\x1a\x96\x94\xe6\x8dT\x868\x9f\xc9\x83h\xed\xef\x98\xb8\xecD\xf1\xf7(]\x10L\xa0\xa0\xaa7\xaf\xad\x13\xf2P\xf2\xd0\x1eP\xa7mC\x05\xb0\xe6\xd2u\xa8\x92\x07mV\'$\x88\xd5\xbd\r\xdb\xb8a\x83O\x802\x11F\xbc\x14\xa5T\xa3\x12\xa6\xfa\xef\x8b\xaf\xa5W\x17\xceA\xe9\x84\xcb=\xb0\xa1\xb2/Fw\xc7\xd2HK\x07]N\\\xd8\x1e\x0b\xdd]\xad\xa8\xec\xa7\x8b\xbdA\x05\xa0\xc1\xd3\xf7\xb6Q\x82\xfe\x9f\x91\xd6\xa5\x95\x98\xcf\x96\xa6\xb4+\xcd\xb5b\xb7\x92\x8c\xdc\x0f\xeb\x9e\x9a\x9b\xb01i\x91\xd3\xfd\xe0\xba\x03\x0c\xf7\xaa\xbb\xe0)\xe3\x16\x1f\xc4\x15\x0f\rJhh\xa3~\xdb\x05F\x0f}[\xf0\x14\xfbqj\xa3iuE\xdd\xbc@\xf68JA\xcc\xa4\x1f\xaa\xee\x9eOAi\xc7%\x81\xf3\xe7Z\xf6+e\xd6YE\x88\xe0\xe8i\x90\xb8\xea$*;4x\xc7\xc1\x8e\x06M\x13\x1b<\x89\xd2\xec]v\x8b\xfb##\xe3\x8b\xf0\xba\xb4\x99\xb2\x16\xf4\xb8\xa2`d\x86\x85c\xa6\x9fEi\xc7%\x81\xba\xd2X\x83Zn\xcaD)H\x80f\x1a\xcc\xfc\xf0\xafN\x7f\xff\xa0c\xe8\xf2\x94\x85\xc4\x1c\x9b\x19\xb8\x9b\x1b\xa2\xb9\xae\xc6r\xce\xd8\x01\x9f\x87\\\x84\x11L\x1f\xbey\x88\\"\x03\xa9\x91]\xb1>a\xfeut\xd9\x11l\xe2\x8a\xfc\xd0#\xc3\x14\xfdn;\x9f\xc7O\x134{[s Y\xd1\x92\xa0M9\xf8/Kk\xbd\x84\x18\xadw{\xd3\x12h3\xf3Ip\\\xbb\n\xee\xed+c\xcc\xf7TRrB\xab\xa4&\xd8\xedw\xc1\xba\x87\x0e\xc8\x8c\\i\xf3\xdb\x07\xaaQ:\xe1\xb6m\xc8E"M+\xea\xd4\xb2\x8c4\x1e]\x82L\xd20\x85\xa5\xcb\xfd?F)\x06-)\xfbLDH\xe6\xb4S\xd2\xa1\xe7\xf2\x07\xa8y\x8c\xe4~.\xbe?\x16\xb6\xb3q\xbcVI\x94:\x06\xb8\xd4\xa6\xec}\x82\xa7\x9d\xe0\x0eP\x18\x86\xb1<;\x9a\xcdTH\xdd\xf7\x03\xe5J3W\x90[\xde\xe5\xd2\\\x02$\x13s\xdc\xa5\xfa\x06\xed\xcd\xf5\xb8\xb6#\x0f\x00x?\xe2\x1a<\xcf\xdc\x07_\xc6\xc70V\x15\x99\xed.x\x8a\xdb\x04(\x85\x8b\xd4M*\x85x?X\xc8\x84q\xbb\x959\xdcx\xbbO\xf4\xcaGh\xb9\xd9G\x8a\xe4\x1c\x83D\xdf\xea:N\xf8\xf8\x01\xec\xd7\xfc\x06\xe3\xc0\x94s<\xf3\x13\xd1\x8bN4\x01J%\xe9\x870\xdf~\xd1Q\xa1\xcf\x04\xaa-G,\xe5(\xc5\xd0ge\xcd\x99\x1d\xa6\x96\x0e\xa0\x86\xa4U&\x10JB\xed\'+=\xfd\xe6\xc1\xefP\xbae\xc8\x04(\x07\xd7\x0e[*\x03\x8b\xe8_\xa2\xa3\x17R\x96\xef\xeeu:a\x84\xa8\xcc\xbf1\xef\xc7"\xa9\x02%\x1f<M\xc2\tY\xb0\x0e\xfc5\x1e\x8d(\x1e%@\xfa\xa1\'i\x14\x9b\xe9\xc3\x88\xf7C\xb3\x81-&7\xb9\xdb~\xe0\xfe\\\x91!a\r[\x93\xe2\x18\xa8/\xf3A\xaf5\x89\xe4w0\tR\xf7\x10<+\x9bI\xd0y2\xa2x\x96\x00\x85\xf6C\x80\xb4\x7f\xc8~\xb8\xa2\xb7\x1c\xfe]\xcf9\xde\xbc<\x9c6%\x14:N\x97\x83\xd9\x1aWR\x9c\x04\x0e\x15>L\xe2\xd3w\xa5$xr\x96\xcaG\xacf\x9e\xfeVt\xb7\x1dy\xa4/\xf5\x94\x8c-\xfa7dJ\x19?j\xd8\xb2\xb7}S\xb0\xe9\xd8\x08\x85n\xc72u\x1dJ\x1e\xee\xec\x98x\x90\xca\x1cv\x87~\x9a\x85\xa5\x05\xcd3*\n5\xbf\xf2\xbf\xc5\'\xd8\x00\x93\x1b\xbe!;.\xfc\xe5\xc3\x8b\x17/^\xbc<^\x00\xfe\x03t\xd3\x12d\xcd*\xb7\xe1\x00\x00\x00\x00IEND\xaeB`\x82'
icon_id = idaapi.load_custom_icon(data=icon, format="png")

class util:
    def syntax_highlight_proto(line):
        colored_line = line
        # Strings
        str = re.search("\".*\"",colored_line)
        if str:
            colored_line = colored_line[:str.start()] + idaapi.COLSTR(colored_line[str.start():str.end()], idaapi.SCOLOR_DSTR) + colored_line[str.end():]
        # Variables
        var = re.search("\w+\s*(?==)",colored_line)
        if var:
            colored_line = colored_line[:var.start()] + idaapi.COLSTR(colored_line[var.start():var.end()], idaapi.SCOLOR_CODNAME) + colored_line[var.end():]
        # Objects
        obj = re.search("\w+\s*(?={)",colored_line)
        if obj:
            colored_line = colored_line[:obj.start()] + idaapi.COLSTR(colored_line[obj.start():obj.end()], idaapi.SCOLOR_IMPNAME) + colored_line[obj.end():]
        # Numbers
        num = re.search("\W\d+\W",colored_line)
        if num:
            colored_line = colored_line[:num.start()] + idaapi.COLSTR(colored_line[num.start():num.end()], idaapi.SCOLOR_DNUM) + colored_line[num.end():]

        return colored_line


# IDA plugin Action Handler
class ProtobufFinder(idaapi.action_handler_t):
    def __init__(self):
        idaapi.action_handler_t.__init__(self)

    # Called when the Search -> Protobuf Finder is clicked
    def activate(self, ctx):
        print("[Protobuf] Search started.")
        runProtod() # run search
        print("[Protobuf] Search done.")
        pass

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


class protobuf_fetch_t(idaapi.plugin_t):
    comment = "Protobuf string finder."
    help = "A plugin to find a parse embedded proto files in binaries with basic syntax highlight."
    wanted_name = "Protobuf"
    wanted_hotkey = ""
    flags = idaapi.PLUGIN_KEEP

    def init(self):
        self.printBanner()

        protobuf_desc = idaapi.action_desc_t(
            'protobuf:search',      # The action name. This acts like an ID and must be unique
            'Protobuf Finder',      # The action text.
             ProtobufFinder(),      # The action handler.
            '',                     # Optional: the action shortcut
            '',                      # Optional: the action tooltip (available in menus/toolbar)
            icon_id)                      # Optional: the action icon (shows when in menus/toolbars)
        idaapi.register_action(protobuf_desc)

        # Insert the action in the menu
        if not idaapi.attach_action_to_menu("Search", "protobuf:search", idaapi.SETMENU_APP):
            print("[Protobuf] Failed attaching to menu.")

        return idaapi.PLUGIN_KEEP
      
    def run(self):
        pass

    def term(self):
        pass

    def printBanner(self):
        print("---------------------------------------------------------------------------------------------")
        print("Protobuf string finder loaded.")
        print("Run via Search -> Protobuf Finder")
        print("---------------------------------------------------------------------------------------------")


def runProtod():
    extractor = ProtobufExtractor()
    extractor.extract()


def PLUGIN_ENTRY():
    return protobuf_fetch_t()


# =======================================================================================
# rewritten Protod code to Python3, using IDA API
# =======================================================================================

###########
# helpers
###########

def is_valid_filename(filename):
    '''
    Check if given filename may be valid
    '''
    charset = 'abcdefghijklmnopqrstuvwxyz0123456789-_/$,.[]()'
    for char in filename.lower():
        if chr(char) not in charset:
            return False
    return True


def decode_varint128(stream):
    '''
    Decode Varint128 from buffer
    '''
    bits = ''
    count = 0
    for stream_byte in stream:
        count += 1
        raw_byte = stream_byte
        bits += (bin((raw_byte&0x7F))[2:]).rjust(7,'0')
        if (raw_byte&0x80) != 0x80:
            break
    return (int(bits, 2), count)


def render_type(field_type, package):
    '''
    Return the string representing a given type inside a given package
    '''
    i = 0
    nodes = field_type.split('.')
    nodes_ = package.split('.')
    for i in range(len(nodes)):
        if i < len(nodes_):
            if nodes[i] != nodes_[i]:
                return '.'.join(nodes[i:])
        else:
            return '.'.join(nodes[i:])
    return '.'.join(nodes[i:])


#############################
# Protobuf fields walker
#############################

class ProtobufFieldsWalker:
    '''
    Homemade Protobuf fields walker

    This class allows Protod to walk the fields
    and determine the probable size of the protobuf
    serialized file.
    '''

    def __init__(self, stream):
        self._stream = stream
        self._size =  -1

    def get_size(self):
        return self._size

    def walk(self):
        end = False
        offset = 0
        while (not end) and (offset<len(self._stream)):
            # read tag
            tag = self._stream[offset]
            offset += 1
            wire_type = tag&0x7
            if wire_type == 0:
                value, size = decode_varint128(self._stream[offset:])
                offset += size
            elif wire_type == 1:
                offset += 8
            elif wire_type == 2:
                value, size = decode_varint128(self._stream[offset:])
                offset += size + value
            elif wire_type == 5:
                offset += 4
            elif wire_type == 3:
                continue
            elif wire_type == 4:
                continue
            else:
                end = True
        self._size = offset-1


#############################
# Serialized metadata parsing
#############################

class FileDescriptorDisassembler:
    '''
    Core disassembling class

    This class parses the provided serialized data and
    produces one or many .proto files.
    '''

    def __init__(self, file_desc):
        self.desc = file_desc

    def getLabel(self, l):
        return [None, 'optional', 'required', 'repeated'][l]

    def getTypeStr(self, t):
        types = [
            None,
            'double',
            'float',
            'int64',
            'uint64',
            'int32',
            'fixed64',
            'fixed32',
            'bool',
            'string',
            'group',
            'message',
            'bytes',
            'uint32',
            'enum',
            'sfixed32',
            'sfixed64',
            'sint32',
            'sint64'
        ]
        return types[t]

    def renderEnum(self, enum, depth=0, package='', nested=False):
        buffer = '\n'
        buffer += '%senum %s {\n' % (' '*depth, enum.name)
        for value in enum.value:
            buffer += '%s%s = %d;\n' % (' '*(depth+1), value.name, value.number)
        buffer += '%s}' % (' '*depth)
        buffer += '\n'
        return buffer

    def renderField(self, field, depth=0, package='', nested=False):
        buffer = ''
        try:
            if field.HasField('type'):
                # message case
                if field.type == FieldDescriptorProto.TYPE_MESSAGE or field.type == FieldDescriptorProto.TYPE_ENUM:
                    field.type_name = render_type(field.type_name[1:], package)
                    buffer += '%s%s %s %s = %d;\n' % (' '*depth, self.getLabel(field.label), field.type_name, field.name, field.number)
                else:
                    if field.HasField('default_value'):
                        if self.getTypeStr(field.type) == 'string':
                            field.default_value = '"%s"'% field.default_value
                        buffer += '%s%s %s %s = %d [default = %s];\n' % (' '*depth, self.getLabel(field.label), self.getTypeStr(field.type), field.name, field.number, field.default_value)
                    else:
                        buffer += '%s%s %s %s = %d;\n' % (' '*depth, self.getLabel(field.label), self.getTypeStr(field.type), field.name, field.number)
        except ValueError:
            buffer += '%smessage %s {\n' % (' '*depth, field.name)
            _package = package+'.'+field.name

            if len(field.nested_type)>0:
                for nested in field.nested_type:
                    buffer += self.renderField(nested, depth+1, _package, nested=True)
            if len(field.enum_type)>0:
                for enum in field.enum_type:
                    buffer += self.renderEnum(enum, depth+1, _package)
            if len(field.field)>0:
                for field in field.field:
                    buffer += self.renderField(field, depth+1, _package)
            buffer += '%s}' % (' '*depth)
            buffer += '\n\n'
        return buffer


    def render(self, filename=None):
        print('[Protobuf][+] Processing %s' % self.desc.name)
        buffer = ''
        buffer += 'package %s;\n\n' % self.desc.package

        # add dependencies
        if len(self.desc.dependency)>0:
            for dependency in self.desc.dependency:
                buffer += 'import "%s";\n' % dependency
            buffer += '\n'

        if len(self.desc.enum_type)>0:
            for enum in self.desc.enum_type:
                buffer += self.renderEnum(enum, package=self.desc.package)
        if len(self.desc.message_type)>0:
            for message in self.desc.message_type:
                buffer += self.renderField(message, package=self.desc.package)
        
        # print result into IDA window
        textWindow = ida_kernwin.simplecustviewer_t()
        if textWindow.Create("Protobuf file: " + self.desc.name):
            for line in buffer.split('\n'):
                textWindow.AddLine(util.syntax_highlight_proto(line))
            textWindow.Show()
        else:
            print ("[Protobuf] Failed to open window - it is probably already open.")


#############################
# Main code
#############################

class ProtobufExtractor:
    def __init__(self):
        pass

    def extract(self):
        protos = []
        searchStartAddr = 0
        
        while True:
            # search binary for ".proto" string
            r = ida_bytes.bin_search(searchStartAddr,ida_ida.MAXADDR,bytes([0x2E, 0x70, 0x72, 0x6F, 0x74,0x6F]),bytes([0xFF,0xFF,0xFF,0xFF,0xFF,0xFF]),1,1)
            if r == idaapi.BADADDR:
                print("[Protobuf][dbg] Search results into BADADDR (not found). Break!")
                break

            searchStartAddr = r+1
            print(f"[Protobuf][dbg] String \".proto\" found at {hex(r)}.")
            
            # Find beginning of the whole proto string
            for j in range(64):
                try:
                    protostring = ida_bytes.get_bytes(r-j-1, MAX_PROTOBUF_SIZE, False)
                    
                    if decode_varint128(protostring[1:])[0]==(j+5) and is_valid_filename(protostring[1+1:j+6+1]):
                        print(f"[Protobuf][dbg] Protostring identified: {protostring[:40]} [...]")
                       
                        # Walk the fields and get a probable size
                        walker = ProtobufFieldsWalker(protostring)
                        walker.walk()
                        probable_size = walker.get_size()
                       
                        print(f"[Protobuf][dbg] Protostring section size {hex(probable_size)}.")
                       
                        """
                        Probable size approach is not perfect,
                        we add a delta of 1024 bytes to be sure
                        not to miss something =)
                        """
                        for k in range(probable_size+1024, 0, -1):
                            try:
                                fds = FileDescriptorProto()
                                fds.ParseFromString(protostring[:k])
                                protos.append(protostring[:k])
                                print('[Protobuf][dbg][i] Found protofile %s (%d bytes)' % (protostring[:j+5], k))
                                break
                            except DecodeError:
                                pass
                            except UnicodeDecodeError:
                                pass
                        break
                except IndexError:
                    pass
        if protos:
            # Load successively each binary proto file and rebuild it from scratch
            seen = []
            for content in protos:
                try:
                    # Load the prototype
                    fds  = FileDescriptorProto()
                    fds.ParseFromString(content)
                    res = FileDescriptorDisassembler(fds)
                    if len(res.desc.name)>0:
                        if res.desc.name not in seen:
                            res.render() # disassemble and show .proto file in IDA window
                            seen.append(res.desc.name)
                except DecodeError:
                    pass
        else:
            ida_kernwin.info("No embedded proto files were discovered.")


repository: Ph0en1x-XMU/Ph0en1x-Team
file: Reverse/ida/IDA Pro 7.5.201028 (x86,x64,ARM,ARM64,PPC,PPC64,MIPS)/python/examples/hexrays/vds17.py
wanted_name: Structure offsets (IDAPython)
comment: Sample plugin17 for Hex-Rays decompiler
created_at: 2022-06-02 11:06:16+00:00
pushed_at: 2022-06-02 11:11:40+00:00
url:
https://github.com/Ph0en1x-XMU/Ph0en1x-Team/blob/a8d80aaea8227e418f5826bf05ef69a1bc8d84f1/Reverse/ida/IDA%20Pro%207.5.201028%20(x86,x64,ARM,ARM64,PPC,PPC64,MIPS)/python/examples/hexrays/vds17.py

content:
#
#      Hex-Rays Decompiler project
#      Copyright (c) 2007-2020 by Hex-Rays, support@hex-rays.com
#      ALL RIGHTS RESERVED.
#
#      Sample plugin for Hex-Rays Decompiler.
#      It shows how to use "Select offsets" widget (select_udt_by_offset() call).
#      This plugin repeats the Alt-Y functionality.
#      Usage: place cursor on the union field and press Shift-T
#
#      This is a rewrite in Python of the vds17 example that comes with hexrays sdk.
#

import ida_idaapi
import ida_hexrays
import ida_lines
import ida_typeinf
import ida_kernwin

# --------------------------------------------------------------------------
class func_stroff_ah_t(ida_kernwin.action_handler_t):
    def __init__(self):
        ida_kernwin.action_handler_t.__init__(self)

    def activate(self, ctx):
        # get the current item
        vu = ida_hexrays.get_widget_vdui(ctx.widget)
        vu.get_current_item(ida_hexrays.USE_KEYBOARD)

        # REGION1, will be referenced later
        # check that the current item is a union field
        if not vu.item.is_citem():
            ida_kernwin.warning("Please position the cursor on a union member")
            return 0
        e = vu.item.e
        while True:
            op = e.op
            if op != ida_hexrays.cot_memptr and op != ida_hexrays.cot_memref:
                ida_kernwin.warning("Please position the cursor on a union member")
                return 0
            e = e.x
            if op == ida_hexrays.cot_memptr:
                if e.type.is_union():
                    break
            else:
                if ida_typeinf.remove_pointer(e.type).is_union():
                    break
            if not e.type.is_udt():
                ida_kernwin.warning("Please position the cursor on a union member")
                return 0
        # END REGION1

        # REGION2
        # calculate the member offset
        off = 0
        e = vu.item.e
        while True:
            e2 = e.x
            tif = ida_typeinf.remove_pointer(e2.type)
            if not tif.is_union():
                off += e.m
            e = e2
            if e2.op != ida_hexrays.cot_memptr and e2.op != ida_hexrays.cot_memref:
                break
            if not e2.type.is_udt():
                break
        # END REGION2

        # REGION3
        # go up and collect more member references (in order to calculate the final offset)
        p = vu.item.e
        while True:
            p2 = vu.cfunc.body.find_parent_of(p)
            if p2.op == ida_hexrays.cot_memptr:
                break
            if p2.op == ida_hexrays.cot_memref:
                e2 = p2.cexpr
                tif = ida_typeinf.remove_pointer(e2.x.type)
                if not tif.is_union():
                    off += e2.m
                p = p2
                continue
            if p2.op == ida_hexrays.cot_ref:
                # handle &a.b + N (this expression may appear if the user previously selected
                #                  a wrong field)
                delta = 0
                add = vu.cfunc.body.find_parent_of(p2)
                if add.op == ida_hexrays.cot_cast:
                    add = vu.cfunc.body.find_parent_of(add)
                if add.op == ida_hexrays.cot_add and add.y.op == ida_hexrays.cot_num:
                    delta = add.y.numval()
                    objsize = add.type.get_ptrarr_objsize()
                    nbytes = delta * objsize
                    off += nbytes
            # we could use helpers like WORD/BYTE/... to calculate a more precise offset
            # if ( p2->op == cot_call && (e2->exflags & EXFL_LVALUE) != 0 )
            break
        # END REGION3

        # REGION4
        ea = vu.item.e.ea
        # the item itself may be unaddressable.
        # TODO: find its addressable parent
        if ea == ida_idaapi.BADADDR:
            ida_kernwin.warning("Sorry, the current item is not addressable")
            return 0
        # END REGION4

        # REGION5
        # prepare the text representation for the item,
        # use the neighborhoods of cursor
        line = ida_lines.tag_remove(ida_kernwin.get_custom_viewer_curline(vu.ct, False))
        line_len = len(line)
        x = max(0, vu.cpos.x - 10)
        l = min(10, line_len - vu.cpos.x) + 10
        line = line[x:x+l]
        # END REGION5

        # REGION6
        ops = ida_hexrays.ui_stroff_ops_t()
        op = ops.push_back()
        op.offset = off
        op.text = line
        # END REGION6

        # REGION7
        class set_union_sel_t(ida_hexrays.ui_stroff_applicator_t):
            def __init__(self, ea):
                ida_hexrays.ui_stroff_applicator_t.__init__(self)
                self.ea = ea

            def apply(self, opnum, path, top_tif, spath):
                typename = ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, top_tif, '', '')
                ida_kernwin.msg("User selected %s of type %s\n" % (spath, typename))
                if path.empty():
                    return False
                vu.cfunc.set_user_union_selection(self.ea, path)
                vu.cfunc.save_user_unions()
                return True
        # END REGION7

        # REGION8
        su = set_union_sel_t(ea)
        res = ida_hexrays.select_udt_by_offset(None, ops, su)
        if res != 0:
            # regenerate ctree
            vu.refresh_view(True)
        # END REGION8

        return 1

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_FOR_WIDGET if \
            ctx.widget_type == ida_kernwin.BWN_PSEUDOCODE else \
            ida_kernwin.AST_DISABLE_FOR_WIDGET


# --------------------------------------------------------------------------
# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "Structure offsets (IDAPython)"
    wanted_hotkey = ""
    comment = "Sample plugin17 for Hex-Rays decompiler"
    help = ""
    def init(self):
        if ida_hexrays.init_hexrays_plugin():
            print("Hex-rays version %s has been detected, Structure offsets ready to use" % ida_hexrays.get_hexrays_version())
            ida_kernwin.register_action(
                ida_kernwin.action_desc_t(
                    "vds17:strchoose",
                    "Structure offsets",
                    func_stroff_ah_t(),
                    "Shift+T"))
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
        pass
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return my_plugin_t()

"""
# A few notes about the VDS17 sample

You can find two VDS17 samples in the IDA Pro install directory:

    python/examples/hexrays/vds17.py
    plugins/hexrays_sdk/plugins/vds17

The former is an IDAPython plugin and the latter is a C++ IDA Pro plugin.
Actually they have the same functionality.
Just to be more concrete the vds17.py plugin will be used below.


## How the user interface works

Let us suppose that we have the following local types:

    1 C     struct {int c0;int c1;}
    2 U     union {int u0;__int16 u1;}
    3 D     struct {int d0;C d1;U d2;}
    4 E     struct {int e0;D e1;}
    5 res_t struct {int r0;int r1;__int16 r2;}

and the decompiler generates the following pseudocode:

    void __cdecl f(res_t *r)
    {
      r->r0 = e->e1.d1.c0;
      r->r1 = e->e1.d2.u0;
      r->r2 = e->e1.d2.u1;
    }

As we see, it looks good, the decompiler did a really good job.

But let us imagine that we need reference to the ```e1.d2.u0``` union member on the last line.

At first we need to load the VDS17 plugin. For that,
place the cursor at the ```u1``` union member on the last line
and use ```Shift-T```.

The "Structure offsets" dialog appears on the screen.
The left pane of the dialog contains the available local types
and the right pane has the following view:

    [checked] e->e1.d2.u1; | 10h | |

Use the left pane to select ```E```, expand it, and select (Double-click) on the ```u0``` member.

The right pane will change to:

    [checked] e->e1.d2.u1; | 10h | [checke] E.e1.d2.u0 |

Since this is what we want, press the ```OK``` button and the pseudocode gets changed:

    void __cdecl f(res_t *r)
    {
      r->r0 = e->e1.d1.c0;
      r->r1 = e->e1.d2.u0;
      r->r2 = e->e1.d2.u0;
    }


### What do we need this plugin for?

All union members have the same offset in the parent structure.
The decompiler selects the first suitable union member.
Sometimes we need to change this selection and use another union member.


## API details

Let us look into ```python/examples/hexrays/vds17.py```.

* REGION1: check that the cursor points to the union member
* REGION2: calculate the member offset
* REGION3: sometimes there is a need to adjust the offset
* REGION4: the pointed item must be addressable

Then the magic begins:

* REGION5: we need something to show in the ```Operand``` column of the right pane.
    The text around the cursor looks like a good compromise.
* REGION6: we are ready to fill the right pane: we have calculated the offset and
    have prepared the descriptive text for the operand.
* REGION7: it is a callback that informs us about the user selection (will be described later)
* REGION8: activate the "Structure offsets" dialog, let the user select,
    the callback specified above will update the union member, refresh pseudocode


### The devil is in the details

The main part of callback is the ```apply``` method (REGION7).
It receives two arguments:

1. ```opnum``` is the number of the selected operand (line) in the right pane of the dialog.
     We need something to map the line number to our operand.
     In the case of VDS17 ```ea``` performs this role.
2. ```path```  the path that describes the union selection.
3. ```top_tif``` typeinfo of the top-level UDT which user selected
4. ```spath``` the field names path to the selected member

The union selection path is denotes a concrete member inside a UDT.

For structure types there is no need in the union selection path because the member
offset uniquely denotes the desired member.

For unions, on the other hand, the member offset is not enough because all union
members start at the same offset zero. For them, we remember the ordinal number of the
union member in the path. E.g., for the local types given above, the following holds:

* e1.d1.c0 is denoted by an empty path because it does not have any unions
* e1.d2.u0 is denoted by path consisting of 0: we use the first member of U
* e1.d2.u1 is denoted by path consisting of 1: we use the second member of U

You can retrieve the union selection path using API call ```get_user_union_selection```
or apply it using ```set_user_union_selection```.
"""


repository: vAlerainTech/vAlerain-Ark
file: cmake-build-debug/IDA_Pro_7.7/python/examples/hexrays/vds3.py
wanted_name: Hex-Rays if-inverter (IDAPython)
comment: Sample plugin3 for Hex-Rays decompiler
created_at: 2024-03-25 14:26:36+00:00
pushed_at: 2024-07-27 05:07:46+00:00
url:
https://github.com/vAlerainTech/vAlerain-Ark/blob/df8bcafeda091d30ceade7bb2c7c73b5ae351a38/cmake-build-debug/IDA_Pro_7.7/python/examples/hexrays/vds3.py

content:
"""
summary: invert if/else blocks

description:
  Registers an action that can be used to invert the `if`
  and `else` blocks of a `ida_hexrays.cif_t`.

  For example, a statement like

      if ( cond )
      {
        statements1;
      }
      else
      {
        statements2;
      }

  will be displayed as

      if ( !cond )
      {
        statements2;
      }
      else
      {
        statements1;
      }

  The modifications are persistent: the user can quit & restart
  IDA, and the changes will be present.

author: EiNSTeiN_ (einstein@g3nius.org)
"""

from __future__ import print_function

import idautils

import ida_kernwin
import ida_hexrays
import ida_netnode
import ida_idaapi
import ida_idp

import traceback

NETNODE_NAME = '$ hexrays-inverted-if'

inverter_actname = "vds3:invert"

class invert_action_handler_t(ida_kernwin.action_handler_t):
    def __init__(self, inverter):
        ida_kernwin.action_handler_t.__init__(self)
        self.inverter = inverter

    def activate(self, ctx):
        vdui = ida_hexrays.get_widget_vdui(ctx.widget)
        self.inverter.invert_if_event(vdui)
        return 1

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_FOR_WIDGET if \
            ctx.widget_type == ida_kernwin.BWN_PSEUDOCODE else \
            ida_kernwin.AST_DISABLE_FOR_WIDGET


class hexrays_callback_info(object):

    def __init__(self):
        self.vu = None

        self.node = ida_netnode.netnode()
        if not self.node.create(NETNODE_NAME):
            # node exists
            self.load()
        else:
            self.stored = []

        return

    def load(self):

        self.stored = []

        try:
            data = self.node.getblob(0, 'I')
            if data:
                self.stored = eval(data.decode("UTF-8"))
                print('Invert-if: Loaded %s' % (repr(self.stored), ))
        except:
            print('Failed to load invert-if locations')
            traceback.print_exc()
            return

        return

    def save(self):

        try:
            self.node.setblob(repr(self.stored).encode("UTF-8"), 0, 'I')
        except:
            print('Failed to save invert-if locations')
            traceback.print_exc()
            return

        return

    def invert_if(self, insn):

        if insn.opname != 'if':
            return False

        cif = insn.details

        if not cif.ithen or not cif.ielse:
            return False

        ida_hexrays.qswap(cif.ithen, cif.ielse)
        # Make a copy of 'cif.expr': 'lnot' might destroy its toplevel
        # cexpr_t and return a pointer to its direct child (but we'll want to
        # 'swap' it later, the 'cif.expr' cexpr_t object must remain valid.)
        cond = ida_hexrays.cexpr_t(cif.expr)
        notcond = ida_hexrays.lnot(cond)

        cif.expr.swap(notcond)

        return True

    def add_location(self, ea):
        if ea in self.stored:
            self.stored.remove(ea)
        else:
            self.stored.append(ea)
        self.save()
        return

    def find_if_statement(self, vu):

        vu.get_current_item(ida_hexrays.USE_KEYBOARD)
        item = vu.item

        if item.is_citem() and item.it.op == ida_hexrays.cit_if and item.it.to_specific_type.cif.ielse is not None:
            return item.it.to_specific_type

        if vu.tail.citype == ida_hexrays.VDI_TAIL and vu.tail.loc.itp == ida_hexrays.ITP_ELSE:
            # for tail marks, we know only the corresponding ea,
            # not the pointer to if-statement
            # find it by walking the whole ctree
            class if_finder_t(ida_hexrays.ctree_visitor_t):
                def __init__(self, ea):
                    ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST | ida_hexrays.CV_INSNS)

                    self.ea = ea
                    self.found = None
                    return

                def visit_insn(self, i):
                    if i.op == ida_hexrays.cit_if and i.ea == self.ea:
                        self.found = i
                        return 1 # stop enumeration
                    return 0

            iff = if_finder_t(vu.tail.loc.ea)
            if iff.apply_to(vu.cfunc.body, None):
                return iff.found

        return

    def invert_if_event(self, vu):

        i = self.find_if_statement(vu)
        if not i:
            return False

        if self.invert_if(i):
            vu.refresh_ctext()
            self.add_location(i.ea)

        return True

    def restore(self, cfunc):

        class visitor(ida_hexrays.ctree_visitor_t):

            def __init__(self, inverter, cfunc):
                ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST | ida_hexrays.CV_INSNS)
                self.inverter = inverter
                self.cfunc = cfunc
                return

            def visit_insn(self, i):
                try:
                    if i.op == ida_hexrays.cit_if and i.ea in self.inverter.stored:
                        self.inverter.invert_if(i)
                except:
                    traceback.print_exc()
                return 0 # continue enumeration

        visitor(self, cfunc).apply_to(cfunc.body, None)

        return


class vds3_hooks_t(ida_hexrays.Hexrays_Hooks):
    def __init__(self, i):
        ida_hexrays.Hexrays_Hooks.__init__(self)
        self.i = i

    def populating_popup(self, widget, phandle, vu):
        ida_kernwin.attach_action_to_popup(vu.ct, None, inverter_actname)
        return 0

    def maturity(self, cfunc, maturity):
        if maturity == ida_hexrays.CMAT_FINAL:
            self.i.restore(cfunc)
        return 0

class idp_hooks_t(ida_idp.IDP_Hooks):
    def __init__(self, i):
        ida_idp.IDP_Hooks.__init__(self)
        self.i = i

    # 'node' refers to index of the named node, this index became invalid after
    # privrange moving, so we recreate the node here to update nodeidx
    def ev_privrange_changed(self, old_privrange, delta):
        i.node.create(NETNODE_NAME)


# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "Hex-Rays if-inverter (IDAPython)"
    wanted_hotkey = ""
    comment = "Sample plugin3 for Hex-Rays decompiler"
    help = ""
    def init(self):
        if ida_hexrays.init_hexrays_plugin():
            i = hexrays_callback_info()
            ida_kernwin.register_action(
                ida_kernwin.action_desc_t(
                    inverter_actname,
                    "Invert then/else",
                    invert_action_handler_t(i),
                    "I"))
            self.vds3_hooks = vds3_hooks_t(i)
            self.vds3_hooks.hook()
            # we need this hook to react to privrange moving event
            self.idp_hooks = idp_hooks_t(i)
            self.idp_hooks.hook()
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
        self.vds3_hooks.unhook()
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return my_plugin_t()


repository: BraveCattle/ctf
file: tools/ida_pro/IDA_Pro_7.7/python/examples/hexrays/vds3.py
wanted_name: Hex-Rays if-inverter (IDAPython)
comment: Sample plugin3 for Hex-Rays decompiler
created_at: 2024-01-23 08:17:51+00:00
pushed_at: 2024-10-23 09:31:30+00:00
url:
https://github.com/BraveCattle/ctf/blob/5858d0da5c048eb8468ff8be1d63fbc50d61b601/tools/ida_pro/IDA_Pro_7.7/python/examples/hexrays/vds3.py

content:
"""
summary: invert if/else blocks

description:
  Registers an action that can be used to invert the `if`
  and `else` blocks of a `ida_hexrays.cif_t`.

  For example, a statement like

      if ( cond )
      {
        statements1;
      }
      else
      {
        statements2;
      }

  will be displayed as

      if ( !cond )
      {
        statements2;
      }
      else
      {
        statements1;
      }

  The modifications are persistent: the user can quit & restart
  IDA, and the changes will be present.

author: EiNSTeiN_ (einstein@g3nius.org)
"""

from __future__ import print_function

import idautils

import ida_kernwin
import ida_hexrays
import ida_netnode
import ida_idaapi
import ida_idp

import traceback

NETNODE_NAME = '$ hexrays-inverted-if'

inverter_actname = "vds3:invert"

class invert_action_handler_t(ida_kernwin.action_handler_t):
    def __init__(self, inverter):
        ida_kernwin.action_handler_t.__init__(self)
        self.inverter = inverter

    def activate(self, ctx):
        vdui = ida_hexrays.get_widget_vdui(ctx.widget)
        self.inverter.invert_if_event(vdui)
        return 1

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_FOR_WIDGET if \
            ctx.widget_type == ida_kernwin.BWN_PSEUDOCODE else \
            ida_kernwin.AST_DISABLE_FOR_WIDGET


class hexrays_callback_info(object):

    def __init__(self):
        self.vu = None

        self.node = ida_netnode.netnode()
        if not self.node.create(NETNODE_NAME):
            # node exists
            self.load()
        else:
            self.stored = []

        return

    def load(self):

        self.stored = []

        try:
            data = self.node.getblob(0, 'I')
            if data:
                self.stored = eval(data.decode("UTF-8"))
                print('Invert-if: Loaded %s' % (repr(self.stored), ))
        except:
            print('Failed to load invert-if locations')
            traceback.print_exc()
            return

        return

    def save(self):

        try:
            self.node.setblob(repr(self.stored).encode("UTF-8"), 0, 'I')
        except:
            print('Failed to save invert-if locations')
            traceback.print_exc()
            return

        return

    def invert_if(self, insn):

        if insn.opname != 'if':
            return False

        cif = insn.details

        if not cif.ithen or not cif.ielse:
            return False

        ida_hexrays.qswap(cif.ithen, cif.ielse)
        # Make a copy of 'cif.expr': 'lnot' might destroy its toplevel
        # cexpr_t and return a pointer to its direct child (but we'll want to
        # 'swap' it later, the 'cif.expr' cexpr_t object must remain valid.)
        cond = ida_hexrays.cexpr_t(cif.expr)
        notcond = ida_hexrays.lnot(cond)

        cif.expr.swap(notcond)

        return True

    def add_location(self, ea):
        if ea in self.stored:
            self.stored.remove(ea)
        else:
            self.stored.append(ea)
        self.save()
        return

    def find_if_statement(self, vu):

        vu.get_current_item(ida_hexrays.USE_KEYBOARD)
        item = vu.item

        if item.is_citem() and item.it.op == ida_hexrays.cit_if and item.it.to_specific_type.cif.ielse is not None:
            return item.it.to_specific_type

        if vu.tail.citype == ida_hexrays.VDI_TAIL and vu.tail.loc.itp == ida_hexrays.ITP_ELSE:
            # for tail marks, we know only the corresponding ea,
            # not the pointer to if-statement
            # find it by walking the whole ctree
            class if_finder_t(ida_hexrays.ctree_visitor_t):
                def __init__(self, ea):
                    ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST | ida_hexrays.CV_INSNS)

                    self.ea = ea
                    self.found = None
                    return

                def visit_insn(self, i):
                    if i.op == ida_hexrays.cit_if and i.ea == self.ea:
                        self.found = i
                        return 1 # stop enumeration
                    return 0

            iff = if_finder_t(vu.tail.loc.ea)
            if iff.apply_to(vu.cfunc.body, None):
                return iff.found

        return

    def invert_if_event(self, vu):

        i = self.find_if_statement(vu)
        if not i:
            return False

        if self.invert_if(i):
            vu.refresh_ctext()
            self.add_location(i.ea)

        return True

    def restore(self, cfunc):

        class visitor(ida_hexrays.ctree_visitor_t):

            def __init__(self, inverter, cfunc):
                ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST | ida_hexrays.CV_INSNS)
                self.inverter = inverter
                self.cfunc = cfunc
                return

            def visit_insn(self, i):
                try:
                    if i.op == ida_hexrays.cit_if and i.ea in self.inverter.stored:
                        self.inverter.invert_if(i)
                except:
                    traceback.print_exc()
                return 0 # continue enumeration

        visitor(self, cfunc).apply_to(cfunc.body, None)

        return


class vds3_hooks_t(ida_hexrays.Hexrays_Hooks):
    def __init__(self, i):
        ida_hexrays.Hexrays_Hooks.__init__(self)
        self.i = i

    def populating_popup(self, widget, phandle, vu):
        ida_kernwin.attach_action_to_popup(vu.ct, None, inverter_actname)
        return 0

    def maturity(self, cfunc, maturity):
        if maturity == ida_hexrays.CMAT_FINAL:
            self.i.restore(cfunc)
        return 0

class idp_hooks_t(ida_idp.IDP_Hooks):
    def __init__(self, i):
        ida_idp.IDP_Hooks.__init__(self)
        self.i = i

    # 'node' refers to index of the named node, this index became invalid after
    # privrange moving, so we recreate the node here to update nodeidx
    def ev_privrange_changed(self, old_privrange, delta):
        i.node.create(NETNODE_NAME)


# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "Hex-Rays if-inverter (IDAPython)"
    wanted_hotkey = ""
    comment = "Sample plugin3 for Hex-Rays decompiler"
    help = ""
    def init(self):
        if ida_hexrays.init_hexrays_plugin():
            i = hexrays_callback_info()
            ida_kernwin.register_action(
                ida_kernwin.action_desc_t(
                    inverter_actname,
                    "Invert then/else",
                    invert_action_handler_t(i),
                    "I"))
            self.vds3_hooks = vds3_hooks_t(i)
            self.vds3_hooks.hook()
            # we need this hook to react to privrange moving event
            self.idp_hooks = idp_hooks_t(i)
            self.idp_hooks.hook()
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
        self.vds3_hooks.unhook()
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return my_plugin_t()


repository: carsond135/malwareanalysis
file: tools/IDA_Pro_7.7/IDA_Pro_7.7/python/examples/hexrays/vds3.py
description: NKU恶意代码分析
wanted_name: Hex-Rays if-inverter (IDAPython)
comment: Sample plugin3 for Hex-Rays decompiler
created_at: 2023-06-25 19:16:29+00:00
pushed_at: 2022-12-18 09:34:14+00:00
url:
https://github.com/carsond135/malwareanalysis/blob/45c8bf862a54239ff1e7c91313eccf9cf7d4a35b/tools/IDA_Pro_7.7/IDA_Pro_7.7/python/examples/hexrays/vds3.py


repository: galaris/tree-cbass
file: SourceCode/trunk/TREE/TREE_Analyzer.py
description: Automatically exported from code.google.com/p/tree-cbass
wanted_name: TREE Analyzer
created_at: 2015-08-04 18:16:44+00:00
pushed_at: 2015-08-04 18:20:06+00:00
url:
https://github.com/galaris/tree-cbass/blob/302c43acdb60753b6f98d0b7f6ab3c314e559f7c/SourceCode/trunk/TREE/TREE_Analyzer.py

content:
#!/usr/bin/python
############################
#
#
############################
import os
import sys
import time

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

from dispatcher.widgets.AnalyzerWidget import AnalyzerWidget
from dispatcher.widgets.VisualizerWidget import VisualizerWidget

HOTKEYS = None
DISPATCHER = None
NAME = "TREE Analyzer v0.2"

class DispatcherForm(PluginForm):
    """
    This class contains the main window of TaintVisualizer QT Graph
    Setup of core modules and widgets will be performed in here
    """
    
    def __init__(self):
        super(DispatcherForm, self).__init__()
        global HOTKEYS
        HOTKEYS = []
        self.dispatcher_widgets = []
        self.idaPluginDir = os.path.join(GetIdaDirectory(),"plugins")
        
        self.iconPath = os.path.join(self.idaPluginDir ,"dispatcher","icons")
        path = os.path.join(self.iconPath ,"dispatcher.png")
        self.icon = QIcon(path)

    def setupWidgets(self):
        """
        Setup dispatcher widgets.
        """
        time_before = time.time()
        print ("[/] setting up widgets...")
        self.dispatcher_widgets.append(AnalyzerWidget(self))
        self.dispatcher_widgets.append(VisualizerWidget(self))
        self.setupDispatcherForm()
        print("[\\] this took %3.2f seconds.\n" % (time.time() - time_before))
        
    def setupDispatcherForm(self):
        """
        Organize the initialized widgets into tabs
        """
        self.tabs = QtGui.QTabWidget()
        self.tabs.setTabsClosable(False)
        for widget in self.dispatcher_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 modules and widgets
        """
        self.printBanner()
        self.parent = self.FormToPySideWidget(form)
        self.parent.setWindowIcon(self.icon)
        self.setupWidgets()
        
    def printBanner(self):
        banner = "#############################################\n" \
               + " ___________________________________________ \n" \
               + " \__    ___/\______   \_   _____/\_   _____/ \n" \
               + "    |    |    |       _/|    __)_  |    __)_ \n" \
               + "    |    |    |    |   \|        \ |        \ \n" \
               + "    |____|    |____|_  /_______  //_______  / \n" \
               + "                     \/        \/         \/  \n" \
               + "#############################################\n" \
               + " Taint-enabled Reverse Engineering Environment\n" \
               + " by Battelle BIT Team                       \n" \
               + "#############################################\n"
        print banner
        print ("[+] Loading TREE Analyzer.QT")
        
    def OnClose(self, form):
        """
        Perform cleanup.
        """
        global DISPATCHER
        del DISPATCHER
        
    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 for widgets
 #########################################################
 
    def setTabFocus(self, widget_name):
        """
        Can be used by Dispatcher widgets to set focus to a widget, identified by name.
        @param widget_name: A widget name
        @type widget_name: STR
        """
        for widget in self.dispatcher_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):
        """
        Used by widgets to register hotkeys.
        Global list of HOTKEYS of function pointers
        Functions cannot take parameters atm
        @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 = "TREE_Analyzer_HOTKEY_%d" % hotkey_index
        HOTKEYS.append(py_function_pointer)
        #self.ida_proxy.CompileLine('static %s() { RunPythonStatement("HOTKEY[%d]()"); }' % (hotkey_name, hotkey_index))
        #self.ida_proxy.Addhotkey(shortcut, hotkey_name)
        
    def passTaintGraph(self, t, widget_name, prop_policy):
        """
        Pass the taintgraph from the analyzer to visualizer
        """
        for widget in self.dispatcher_widgets:
            if widget.name == widget_name:
                widget.setTaintGraph(t, prop_policy)
                
    def passBranchData(self, in_taint_chain, widget_name):
        """
        Pass the taintgraph from the analyzer to visualizer
        """
        for widget in self.dispatcher_widgets:
            if widget.name == widget_name:
                widget.setBranchData(in_taint_chain)
####################################################################
#   Plugin
####################################################################
def PLUGIN_ENTRY():
    return DispatcherPlugin()
    
class DispatcherPlugin(plugin_t):
    """
    Plugin version.
    """
    flags = idaapi.PLUGIN_UNL
    comment = NAME
    help = ""
    wanted_name = "TREE Analyzer"
    wanted_hotkey = "Ctrl-F4"
    
    def init(self):
        self.icon_id = 0
        ExTraces = None
        ExTraces = idaapi.netnode("$ ExTraces", 0, False) #Get the execution trace id
        data = ExTraces.getblob(0, 'A') #Get the execution trace data, use str(data) to convert to data to a str
        if (data is None):
            print "This IDB has no TREE trace. Turn OFF TREE Analyzer"        
            return idaapi.PLUGIN_SKIP
        else:
            print "This IDB has TREE trace in. Turn ON TREE Analyzer!" 
            return idaapi.PLUGIN_OK
        
    def run(self, arg=0):
        f = DispatcherForm()
        f.Show()
        return
        
    def term(self):
        pass
####################################################################
#   Script usage
####################################################################

def main():

    global DISPATCHER
    try:
        DISPATCHER
        DISPATCHER.OnClose(DISPATCHER)
        print ("reloading Dispatcher")
        DISPATCHER = DispatcherForm()
        return
    except Exception:
        DISPATCHER = DispatcherForm()
    """    
    if DISPATCHER.config.dispatcher_plugin_only:
        print "Dispatcher: configured as plugin-only mode, ignoring main function of script. " \
            + "This can be changed in \"cida/config.py\"."
    else:
        DISPATCHER.Show()
    """
    DISPATCHER.Show()
    
if __name__ == "__main__":
    main()


repository: AmesianX/tree-cbass
file: SourceCode/trunk/TREE/TREE_Analyzer.py
description: Automatically exported from code.google.com/p/tree-cbass
wanted_name: TREE Analyzer
created_at: 2015-06-20 22:32:10+00:00
pushed_at: 2015-06-20 22:39:43+00:00
url:
https://github.com/AmesianX/tree-cbass/blob/302c43acdb60753b6f98d0b7f6ab3c314e559f7c/SourceCode/trunk/TREE/TREE_Analyzer.py

content:
#!/usr/bin/python
############################
#
#
############################
import os
import sys
import time

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

from dispatcher.widgets.AnalyzerWidget import AnalyzerWidget
from dispatcher.widgets.VisualizerWidget import VisualizerWidget

HOTKEYS = None
DISPATCHER = None
NAME = "TREE Analyzer v0.2"

class DispatcherForm(PluginForm):
    """
    This class contains the main window of TaintVisualizer QT Graph
    Setup of core modules and widgets will be performed in here
    """
    
    def __init__(self):
        super(DispatcherForm, self).__init__()
        global HOTKEYS
        HOTKEYS = []
        self.dispatcher_widgets = []
        self.idaPluginDir = os.path.join(GetIdaDirectory(),"plugins")
        
        self.iconPath = os.path.join(self.idaPluginDir ,"dispatcher","icons")
        path = os.path.join(self.iconPath ,"dispatcher.png")
        self.icon = QIcon(path)

    def setupWidgets(self):
        """
        Setup dispatcher widgets.
        """
        time_before = time.time()
        print ("[/] setting up widgets...")
        self.dispatcher_widgets.append(AnalyzerWidget(self))
        self.dispatcher_widgets.append(VisualizerWidget(self))
        self.setupDispatcherForm()
        print("[\\] this took %3.2f seconds.\n" % (time.time() - time_before))
        
    def setupDispatcherForm(self):
        """
        Organize the initialized widgets into tabs
        """
        self.tabs = QtGui.QTabWidget()
        self.tabs.setTabsClosable(False)
        for widget in self.dispatcher_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 modules and widgets
        """
        self.printBanner()
        self.parent = self.FormToPySideWidget(form)
        self.parent.setWindowIcon(self.icon)
        self.setupWidgets()
        
    def printBanner(self):
        banner = "#############################################\n" \
               + " ___________________________________________ \n" \
               + " \__    ___/\______   \_   _____/\_   _____/ \n" \
               + "    |    |    |       _/|    __)_  |    __)_ \n" \
               + "    |    |    |    |   \|        \ |        \ \n" \
               + "    |____|    |____|_  /_______  //_______  / \n" \
               + "                     \/        \/         \/  \n" \
               + "#############################################\n" \
               + " Taint-enabled Reverse Engineering Environment\n" \
               + " by Battelle BIT Team                       \n" \
               + "#############################################\n"
        print banner
        print ("[+] Loading TREE Analyzer.QT")
        
    def OnClose(self, form):
        """
        Perform cleanup.
        """
        global DISPATCHER
        del DISPATCHER
        
    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 for widgets
 #########################################################
 
    def setTabFocus(self, widget_name):
        """
        Can be used by Dispatcher widgets to set focus to a widget, identified by name.
        @param widget_name: A widget name
        @type widget_name: STR
        """
        for widget in self.dispatcher_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):
        """
        Used by widgets to register hotkeys.
        Global list of HOTKEYS of function pointers
        Functions cannot take parameters atm
        @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 = "TREE_Analyzer_HOTKEY_%d" % hotkey_index
        HOTKEYS.append(py_function_pointer)
        #self.ida_proxy.CompileLine('static %s() { RunPythonStatement("HOTKEY[%d]()"); }' % (hotkey_name, hotkey_index))
        #self.ida_proxy.Addhotkey(shortcut, hotkey_name)
        
    def passTaintGraph(self, t, widget_name, prop_policy):
        """
        Pass the taintgraph from the analyzer to visualizer
        """
        for widget in self.dispatcher_widgets:
            if widget.name == widget_name:
                widget.setTaintGraph(t, prop_policy)
                
    def passBranchData(self, in_taint_chain, widget_name):
        """
        Pass the taintgraph from the analyzer to visualizer
        """
        for widget in self.dispatcher_widgets:
            if widget.name == widget_name:
                widget.setBranchData(in_taint_chain)
####################################################################
#   Plugin
####################################################################
def PLUGIN_ENTRY():
    return DispatcherPlugin()
    
class DispatcherPlugin(plugin_t):
    """
    Plugin version.
    """
    flags = idaapi.PLUGIN_UNL
    comment = NAME
    help = ""
    wanted_name = "TREE Analyzer"
    wanted_hotkey = "Ctrl-F4"
    
    def init(self):
        self.icon_id = 0
        ExTraces = None
        ExTraces = idaapi.netnode("$ ExTraces", 0, False) #Get the execution trace id
        data = ExTraces.getblob(0, 'A') #Get the execution trace data, use str(data) to convert to data to a str
        if (data is None):
            print "This IDB has no TREE trace. Turn OFF TREE Analyzer"        
            return idaapi.PLUGIN_SKIP
        else:
            print "This IDB has TREE trace in. Turn ON TREE Analyzer!" 
            return idaapi.PLUGIN_OK
        
    def run(self, arg=0):
        f = DispatcherForm()
        f.Show()
        return
        
    def term(self):
        pass
####################################################################
#   Script usage
####################################################################

def main():

    global DISPATCHER
    try:
        DISPATCHER
        DISPATCHER.OnClose(DISPATCHER)
        print ("reloading Dispatcher")
        DISPATCHER = DispatcherForm()
        return
    except Exception:
        DISPATCHER = DispatcherForm()
    """    
    if DISPATCHER.config.dispatcher_plugin_only:
        print "Dispatcher: configured as plugin-only mode, ignoring main function of script. " \
            + "This can be changed in \"cida/config.py\"."
    else:
        DISPATCHER.Show()
    """
    DISPATCHER.Show()
    
if __name__ == "__main__":
    main()


repository: stevemk14ebr/RETools
file: IdaScripts/plugins/microavx.py
description: My reversing tools. Some custom, some not.
wanted_name: MicroAVX
comment: AVX support for the Hex-Rays x64 Decompiler
created_at: 2020-05-27 21:10:30+00:00
pushed_at: 2024-01-03 21:54:17+00:00
url:
https://github.com/stevemk14ebr/RETools/blob/9501332bf02688d3a4b31d85a25349671767be98/IdaScripts/plugins/microavx.py

content:
import sys

import idc
import ida_ua
import ida_ida
import ida_idp
import ida_funcs
import ida_allins
import ida_idaapi
import ida_loader
import ida_kernwin
import ida_typeinf
import ida_hexrays

#-----------------------------------------------------------------------------
# Util
#-----------------------------------------------------------------------------

# an empty / NULL mop_t
NO_MOP = ida_hexrays.mop_t()

# EVEX-encoded instruction, intel.hpp (ida sdk)
AUX_EVEX = 0x10000

# register widths (bytes)
XMM_SIZE = 16
YMM_SIZE = 32
ZMM_SIZE = 64

# type sizes (bytes)
FLOAT_SIZE = 4
DOUBLE_SIZE = 8
DWORD_SIZE = 4
QWORD_SIZE = 8

def size_of_operand(op):
    """
    From ...
        https://reverseengineering.stackexchange.com/questions/19843/how-can-i-get-the-byte-size-of-an-operand-in-ida-pro
    """
    tbyte = 8
    dt_ldbl = 8
    n_bytes = [ 1, 2, 4, 4, 8,
            tbyte, -1, 8, 16, -1,
            -1, 6, -1, 4, 4,
            dt_ldbl, 32, 64 ]
    return n_bytes[op.dtype]

def is_amd64_idb():
    """
    Return true if an x86_64 IDB is open.
    """
    if ida_idp.ph.id != ida_idp.PLFM_386:
        return False
    return ida_ida.cvar.inf.is_64bit()

def bytes2bits(n):
    """
    Return the number of bits repersented by 'n' bytes.
    """
    return n * 8

def is_mem_op(op):
    """
    Return true if the given operand *looks* like a mem op.
    """
    return op.type in [ida_ua.o_mem, ida_ua.o_displ, ida_ua.o_phrase]

def is_reg_op(op):
    """
    Return true if the given operand is a register.
    """
    return op.type in [ida_ua.o_reg]

def is_avx_reg(op):
    """
    Return true if the given operand is a XMM or YMM register.
    """
    return bool(is_xmm_reg(op) or is_ymm_reg(op))

def is_xmm_reg(op):
    """
    Return true if the given operand is a XMM register.
    """
    if op.type != ida_ua.o_reg:
        return False
    if op.dtype != ida_ua.dt_byte16:
        return False
    return True

def is_ymm_reg(op):
    """
    Return true if the given operand is a YMM register.
    """
    if op.type != ida_ua.o_reg:
        return False
    if op.dtype != ida_ua.dt_byte32:
        return False
    return True

def is_avx_512(insn):
    """
    Return true if the given insn_t is an AVX512 instruction.
    """
    return bool(insn.auxpref & AUX_EVEX)

#-----------------------------------------------------------------------------
# Microcode Helpers
#-----------------------------------------------------------------------------

def get_ymm_mreg(xmm_mreg):
    """
    Return the YMM microcode register for a given XMM register.
    """
    xmm_reg = ida_hexrays.mreg2reg(xmm_mreg, XMM_SIZE)
    xmm_name = ida_idp.get_reg_name(xmm_reg, XMM_SIZE)
    xmm_number = int(xmm_name.split("mm")[-1])

    # compute the ymm mreg id
    ymm_reg = ida_idp.str2reg("ymm%u" % xmm_number)
    ymm_mreg = ida_hexrays.reg2mreg(ymm_reg)

    # sanity check...
    xmm_name = ida_hexrays.get_mreg_name(xmm_mreg, XMM_SIZE)
    ymm_name = ida_hexrays.get_mreg_name(ymm_mreg, YMM_SIZE)
    assert xmm_name[1:] == ymm_name[1:], "Reg escalation did not work... (%s, %s)" % (xmm_name, ymm_name)
 
    # return the ymm microcode register id
    return ymm_mreg

def clear_upper(cdg, xmm_mreg, op_size=XMM_SIZE):
    """
    Extend the given xmm reg, clearing the upper bits (through ymm).
    """
    ymm_mreg = get_ymm_mreg(xmm_mreg)

    xmm_mop = ida_hexrays.mop_t(xmm_mreg, op_size)
    ymm_mop = ida_hexrays.mop_t(ymm_mreg, YMM_SIZE)

    return cdg.emit(ida_hexrays.m_xdu, xmm_mop, NO_MOP, ymm_mop)

def store_operand_hack(cdg, op_num, new_mop):
    """
    XXX: why is there a load_operand(), but no inverse.. ?
    """

    # emit a 'load' operation...
    memX = cdg.load_operand(op_num)
    assert memX != ida_hexrays.mr_none, "Invalid op_num..."

    # since this is gonna be kind of hacky, let's make sure a load was actually emitted
    ins = cdg.mb.tail
    if ins.opcode != ida_hexrays.m_ldx:
        if ins.prev.opcode != ida_hexrays.m_ldx:
            raise ValueError("Hehe, hack failed :-( (insn 0x%08X op 0x%02X)" % (cdg.insn.ea, ins.opcode))
        prev = ins.prev
        cdg.mb.make_nop(ins)
        ins = prev
    assert ins.d.size == new_mop.size, "%u vs %u" % (new_mop.size, ins.d.size)

    # convert the load to a store :^)
    ins.opcode = ida_hexrays.m_stx
    ins.d = ins.r   # d = op mem offset
    ins.r = ins.l   # r = op mem segm 
    ins.l = new_mop # l = value to store (mop_t)

    return ins

#-----------------------------------------------------------------------------
# Intrinsic Helper
#-----------------------------------------------------------------------------

class AVXIntrinsic(object):
    """
    This class helps with generating simple intrinsic calls in microcode.
    """

    def __init__(self, cdg, name):
        self.cdg = cdg

        # call info, sort of like func_type_data_t()
        self.call_info = ida_hexrays.mcallinfo_t()
        self.call_info.cc = ida_typeinf.CM_CC_FASTCALL
        self.call_info.callee = ida_idaapi.BADADDR
        self.call_info.solid_args = 0
        self.call_info.role = ida_hexrays.ROLE_UNK
        self.call_info.flags = ida_hexrays.FCI_SPLOK | ida_hexrays.FCI_FINAL | ida_hexrays.FCI_PROP

        # the actual 'call' microcode insn
        self.call_insn = ida_hexrays.minsn_t(cdg.insn.ea)
        self.call_insn.opcode = ida_hexrays.m_call
        self.call_insn.l.make_helper(name)
        self.call_insn.d.t = ida_hexrays.mop_f
        self.call_insn.d.f = self.call_info

        # temp return type
        self.call_info.return_type = ida_typeinf.tinfo_t()
        self.call_insn.d.size = 0

    def set_return_reg(self, mreg, type_string):
        """
        Set the return register of the function call, with a type string.
        """
        ret_tinfo = ida_typeinf.tinfo_t()
        ret_tinfo.get_named_type(None, type_string)
        return self.set_return_reg_type(mreg, ret_tinfo)

    def set_return_reg_basic(self, mreg, basic_type):
        """
        Set the return register of the function call, with a basic type assigned.
        """
        ret_tinfo = ida_typeinf.tinfo_t(basic_type)
        return self.set_return_reg_type(mreg, ret_tinfo)

    def set_return_reg_type(self, mreg, ret_tinfo):
        """
        Set the return register of the function call, with a complex type.
        """
        self.call_info.return_type = ret_tinfo
        self.call_insn.d.size = ret_tinfo.get_size()

        self.mov_insn = ida_hexrays.minsn_t(self.cdg.insn.ea)
        self.mov_insn.opcode = ida_hexrays.m_mov
        self.mov_insn.l.t = ida_hexrays.mop_d
        self.mov_insn.l.d = self.call_insn
        self.mov_insn.l.size = self.call_insn.d.size
        self.mov_insn.d.t = ida_hexrays.mop_r
        self.mov_insn.d.r = mreg
        self.mov_insn.d.size = self.call_insn.d.size

        if ret_tinfo.is_decl_floating():
            self.mov_insn.set_fpinsn()

    def add_argument_reg(self, mreg, type_string):
        """
        Add a regeister argument with a given type string to the function argument list.
        """
        op_tinfo = ida_typeinf.tinfo_t()
        op_tinfo.get_named_type(None, type_string)
        return self.add_argument_reg_type(mreg, op_tinfo)

    def add_argument_reg_basic(self, mreg, basic_type):
        """
        Add a regeister argument with a basic type to the function argument list.
        """
        op_tinfo = ida_typeinf.tinfo_t(basic_type)
        return self.add_argument_reg_type(mreg, op_tinfo)

    def add_argument_reg_type(self, mreg, op_tinfo):
        """
        Add a register argument of the given type to the function argument list.
        """
        call_arg = ida_hexrays.mcallarg_t()
        call_arg.t = ida_hexrays.mop_r
        call_arg.r = mreg
        call_arg.type = op_tinfo
        call_arg.size = op_tinfo.get_size()

        self.call_info.args.push_back(call_arg)
        self.call_info.solid_args += 1

    def add_argument_imm(self, value, basic_type):
        """
        Add an immediate value to the function argument list.
        """
        op_tinfo = ida_typeinf.tinfo_t(basic_type)

        mop_imm = ida_hexrays.mop_t()
        mop_imm.make_number(value, op_tinfo.get_size())
        
        call_arg = ida_hexrays.mcallarg_t()
        call_arg.make_number(value, op_tinfo.get_size())
        call_arg.type = op_tinfo

        self.call_info.args.push_back(call_arg)
        self.call_info.solid_args += 1

    def emit(self):
        """
        Emit the intrinsic call to the generated microcode.
        """
        self.cdg.mb.insert_into_block(self.mov_insn, self.cdg.mb.tail)

#-----------------------------------------------------------------------------
# AVX Lifter
#-----------------------------------------------------------------------------

class AVXLifter(ida_hexrays.microcode_filter_t):
    """
    A Hex-Rays microcode filter to lift AVX instructions during decompilation.
    """

    def __init__(self):
        super(AVXLifter, self).__init__()
        self._avx_handlers = \
        {

            # Compares (Scalar, Single / Double-Precision)
            ida_allins.NN_vcomiss: self.vcomiss,
            ida_allins.NN_vcomisd: self.vcomisd,
            ida_allins.NN_vucomiss: self.vucomiss,
            ida_allins.NN_vucomisd: self.vucomisd,

            # Conversions
            ida_allins.NN_vcvttss2si: self.vcvttss2si,
            ida_allins.NN_vcvtdq2ps: self.vcvtdq2ps,
            ida_allins.NN_vcvtsi2ss: self.vcvtsi2ss,
            ida_allins.NN_vcvtps2pd: self.vcvtps2pd,
            ida_allins.NN_vcvtss2sd: self.vcvtss2sd,

            # Mov (DWORD / QWORD)
            ida_allins.NN_vmovd: self.vmovd,
            ida_allins.NN_vmovq: self.vmovq,

            # Mov (Scalar, Single / Double-Precision)
            ida_allins.NN_vmovss: self.vmovss,
            ida_allins.NN_vmovsd: self.vmovsd,

            # Mov (Packed Single-Precision, Packed Integers)
            ida_allins.NN_vmovaps: self.v_mov_ps_dq,
            ida_allins.NN_vmovups: self.v_mov_ps_dq,
            ida_allins.NN_vmovdqa: self.v_mov_ps_dq,
            ida_allins.NN_vmovdqu: self.v_mov_ps_dq,

            # Bitwise (Packed Single-Precision)
            ida_allins.NN_vorps: self.v_bitwise_ps,
            ida_allins.NN_vandps: self.v_bitwise_ps,
            ida_allins.NN_vxorps: self.v_bitwise_ps,

            # Math (Scalar Single-Precision)
            ida_allins.NN_vaddss: self.v_math_ss,
            ida_allins.NN_vsubss: self.v_math_ss,
            ida_allins.NN_vmulss: self.v_math_ss,
            ida_allins.NN_vdivss: self.v_math_ss,

            # Math (Scalar Double-Precision)
            ida_allins.NN_vaddsd: self.v_math_sd,
            ida_allins.NN_vsubsd: self.v_math_sd,
            ida_allins.NN_vmulsd: self.v_math_sd,
            ida_allins.NN_vdivsd: self.v_math_sd,

            # Math (Packed Single-Precision)
            ida_allins.NN_vaddps: self.v_math_ps,
            ida_allins.NN_vsubps: self.v_math_ps,
            ida_allins.NN_vmulps: self.v_math_ps,
            ida_allins.NN_vdivps: self.v_math_ps,

            # Square Root
            ida_allins.NN_vsqrtss: self.vsqrtss,
            ida_allins.NN_vsqrtps: self.vsqrtps,

            # Shuffle (Packed Single-Precision) 
            ida_allins.NN_vshufps: self.vshufps,

        }

    def match(self, cdg):
        """
        Return true if the lifter supports this AVX instruction.
        """
        if is_avx_512(cdg.insn):
            return False
        return cdg.insn.itype in self._avx_handlers

    def apply(self, cdg):
        """
        Generate microcode for the current instruction.
        """
        cdg.store_operand = lambda x, y: store_operand_hack(cdg, x, y)
        return self._avx_handlers[cdg.insn.itype](cdg, cdg.insn)

    def install(self):
        """
        Install the AVX codegen lifter.
        """
        ida_hexrays.install_microcode_filter(self, True)
        print("Installed AVX lifter... (%u instr supported)" % len(self._avx_handlers))

    def remove(self):
        """
        Remove the AVX codegen lifter.
        """
        ida_hexrays.install_microcode_filter(self, False)
        print("Removed AVX lifter...")

    #--------------------------------------------------------------------------
    # Compare Instructions
    #--------------------------------------------------------------------------

    #
    # the intel manual states that all of these comparison instructions are
    # effectively identical to their SSE counterparts. because of this, we
    # simply twiddle the decoded insn to make it appear as SSE and bail.
    #
    # since the decompiler appears to operate on the same decoded instruction
    # data that we meddled with, it will lift the instruction in the same way
    # it would lift the SSE version we alias each AVX one to.
    #

    def vcomiss(self, cdg, insn):
        """
        VCOMISS xmm1, xmm2/m32
        """
        insn.itype = ida_allins.NN_comiss
        return ida_hexrays.MERR_INSN

    def vucomiss(self, cdg, insn):
        """
        VUCOMISS xmm1, xmm2/m32
        """
        insn.itype = ida_allins.NN_ucomiss
        return ida_hexrays.MERR_INSN

    def vcomisd(self, cdg, insn):
        """
        VCOMISD xmm1, xmm2/m64
        """
        insn.itype = ida_allins.NN_comisd
        return ida_hexrays.MERR_INSN

    def vucomisd(self, cdg, insn):
        """
        VUCOMISD xmm1, xmm2/m64
        """
        insn.itype = ida_allins.NN_ucomisd
        return ida_hexrays.MERR_INSN

    #-------------------------------------------------------------------------
    # Conversion Instructions
    #-------------------------------------------------------------------------

    def vcvttss2si(self, cdg, insn):
        """
        CVTTSS2SI r64, xmm1/m32
        CVTTSS2SI r32, xmm1/m32
        """
        insn.itype = ida_allins.NN_cvttss2si
        return ida_hexrays.MERR_INSN

    def vcvtdq2ps(self, cdg, insn):
        """
        VCVTDQ2PS xmm1, xmm2/m128
        VCVTDQ2PS ymm1, ymm2/m256
        """
        op_size = XMM_SIZE if is_xmm_reg(insn.Op1) else YMM_SIZE

        # op2 -- m128/m256
        if is_mem_op(insn.Op2):
            r_reg = cdg.load_operand(1)

        # op2 -- xmm2/ymm2
        else:
            assert is_avx_reg(insn.Op2)
            r_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1/ymm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        #
        # intrinsics:
        #     __m128 _mm_cvtepi32_ps (__m128i a)
        #     __m256 _mm256_cvtepi32_ps (__m256i a)
        #

        bit_size = bytes2bits(op_size)
        bit_str = str(bit_size) if op_size == YMM_SIZE else ""
        intrinsic_name = "_mm%s_cvtepi32_ps" % bit_str

        avx_intrinsic = AVXIntrinsic(cdg, intrinsic_name)
        avx_intrinsic.add_argument_reg(r_reg, "__m%ui" % bit_size)
        avx_intrinsic.set_return_reg(d_reg, "__m%u" % bit_size)
        avx_intrinsic.emit()

        # clear upper 128 bits of ymm1
        if op_size == XMM_SIZE:
            clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    def vcvtsi2ss(self, cdg, insn):
        """
        VCVTSI2SS xmm1, xmm2, r/m32
        VCVTSI2SS xmm1, xmm2, r/m64
        """
        src_size = size_of_operand(insn.Op3)

        # op3 -- m32/m64
        if is_mem_op(insn.Op3):
            r_reg = cdg.load_operand(2)

        # op3 -- r32/r64
        else:
            assert is_reg_op(insn.Op3)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

        # op2 -- xmm2 
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        # create a temp register to compute the final result into
        t0_result = cdg.mba.alloc_kreg(XMM_SIZE)
        t0_mop = ida_hexrays.mop_t(t0_result, FLOAT_SIZE)

        # create a temp register to downcast a double to a float (if needed)
        t1_i2f = cdg.mba.alloc_kreg(src_size)
        t1_mop = ida_hexrays.mop_t(t1_i2f, src_size)

        # copy xmm2 into the temp result reg, as we need its upper 3 dwords
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, l_reg, 0, t0_result, 0)

        # convert the integer (op3) to a float/double depending on its size
        cdg.emit(ida_hexrays.m_i2f, src_size, r_reg, 0, t1_i2f, 0)

        # reduce precision on the converted floating point value if needed (only r64/m64)
        cdg.emit(ida_hexrays.m_f2f, t1_mop, NO_MOP, t0_mop)

        # transfer the fully computed temp register to the real dest reg 
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, t0_result, 0, d_reg, 0)
        cdg.mba.free_kreg(t0_result, XMM_SIZE)
        cdg.mba.free_kreg(t1_i2f, src_size)
        
        # clear upper 128 bits of ymm1
        clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    def vcvtps2pd(self, cdg, insn):
        """
        VCVTPS2PD xmm1, xmm2/m64
        VCVTPS2PD ymm1, ymm2/m128
        """
        src_size = QWORD_SIZE if is_xmm_reg(insn.Op1) else XMM_SIZE

        # op2 -- m64/m128
        if is_mem_op(insn.Op2):
            r_reg = cdg.load_operand(1)

        # op2 -- xmm2/ymm2
        else:
            assert is_avx_reg(insn.Op2)
            r_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1/ymm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        #
        # intrinsics:
        #   - __m128d _mm_cvtps_pd (__m128 a)
        #   - __m256d _mm256_cvtps_pd (__m128 a)
        #
  
        bit_size = bytes2bits(src_size * 2)
        bit_str = "256" if (src_size * 2) == YMM_SIZE else ""
        intrinsic_name = "_mm%s_cvtps_pd" % bit_str

        avx_intrinsic = AVXIntrinsic(cdg, intrinsic_name)
        avx_intrinsic.add_argument_reg(r_reg, "__m128")
        avx_intrinsic.set_return_reg(d_reg, "__m%ud" % bit_size)
        avx_intrinsic.emit()
        
        # clear upper 128 bits of ymm1
        if src_size == QWORD_SIZE:
            clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    def vcvtss2sd(self, cdg, insn):
        """
        VCVTSS2SD xmm1, xmm2, r/m32
        """

        # op3 -- m32
        if is_mem_op(insn.Op3):
            r_reg = cdg.load_operand(2)

        # op3 -- r32
        else:
            assert is_reg_op(insn.Op3)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)
        
        r_mop = ida_hexrays.mop_t(r_reg, FLOAT_SIZE)

        # op2 -- xmm2 
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        # create a temp register to compute the final result into
        t0_result = cdg.mba.alloc_kreg(XMM_SIZE)
        t0_mop = ida_hexrays.mop_t(t0_result, DOUBLE_SIZE)

        # copy xmm2 into the temp result reg, as we need its upper quadword
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, l_reg, 0, t0_result, 0)

        # convert float (op3) to a double, storing it in the lower 64 of the temp result reg
        cdg.emit(ida_hexrays.m_f2f, r_mop, NO_MOP, t0_mop)

        # transfer the fully computed temp register to the real dest reg 
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, t0_result, 0, d_reg, 0)
        cdg.mba.free_kreg(t0_result, XMM_SIZE)

        # clear upper 128 bits of ymm1
        clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    #-------------------------------------------------------------------------
    # Mov Instructions
    #-------------------------------------------------------------------------

    def vmovss(self, cdg, insn):
        """
        VMOVSS xmm1, xmm2, xmm3	
        VMOVSS xmm1, m32
        VMOVSS xmm1, xmm2, xmm3	
        VMOVSS m32, xmm1
        """
        return self._vmov_ss_sd(cdg, insn, FLOAT_SIZE)

    def vmovsd(self, cdg, insn):
        """
        VMOVSD xmm1, xmm2, xmm3
        VMOVSD xmm1, m64
        VMOVSD xmm1, xmm2, xmm3
        VMOVSD m64, xmm1
        """
        return self._vmov_ss_sd(cdg, insn, DOUBLE_SIZE)

    def _vmov_ss_sd(self, cdg, insn, data_size):
        """
        Templated handler for scalar float/double mov instructions.
        """

        # op form: X, Y -- (2 operands)
        if insn.Op3.type == ida_ua.o_void:

            # op form: xmm1, m32/m64
            if is_xmm_reg(insn.Op1):
                assert is_mem_op(insn.Op2)

                # op2 -- m32/m64
                l_reg = cdg.load_operand(1)
                l_mop = ida_hexrays.mop_t(l_reg, data_size)

                # op1 -- xmm1
                d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
                d_mop = ida_hexrays.mop_t(d_reg, XMM_SIZE)

                # xmm1[:data_size] = [mem]
                insn = cdg.emit(ida_hexrays.m_xdu, l_mop, NO_MOP, d_mop)
        
                # clear xmm1[data_size:] bits (through ymm1)
                clear_upper(cdg, d_reg, data_size)

                return ida_hexrays.MERR_OK

            # op form: m32/m64, xmm1
            else:
                assert is_mem_op(insn.Op1) and is_xmm_reg(insn.Op2)

                # op2 -- xmm1
                l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
                l_mop = ida_hexrays.mop_t(l_reg, data_size)

                # store xmm1[:data_size] into memory at [m32/m64] (op1)
                insn = cdg.store_operand(0, l_mop)
                insn.set_fpinsn()

                return ida_hexrays.MERR_OK

        # op form: xmm1, xmm2, xmm3 -- (3 operands)
        else:
            assert is_xmm_reg(insn.Op1) and is_xmm_reg(insn.Op2) and is_xmm_reg(insn.Op3)

            d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
            l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

            # create a temp register to compute the final result into
            t0_result = cdg.mba.alloc_kreg(XMM_SIZE)

            # emit the microcode for this insn
            cdg.emit(ida_hexrays.m_mov, XMM_SIZE, l_reg, 0, t0_result, 0)
            cdg.emit(ida_hexrays.m_f2f, data_size, r_reg, 0, t0_result, 0)
            cdg.emit(ida_hexrays.m_mov, XMM_SIZE, t0_result, 0, d_reg, 0)
            cdg.mba.free_kreg(t0_result, XMM_SIZE)
                
            # clear xmm1[data_size:] bits (through ymm1)
            clear_upper(cdg, d_reg, data_size)

            return ida_hexrays.MERR_OK

        # failsafe
        assert "Unreachable..."
        return ida_hexrays.MERR_INSN 

    def vmovd(self, cdg, insn):
        """
        VMOVD xmm1, r32/m32
        VMOVD r32/m32, xmm1
        """
        return self._vmov(cdg, insn, DWORD_SIZE)
    
    def vmovq(self, cdg, insn):
        """
        VMOVQ xmm1, r64/m64
        VMOVQ r64/m64, xmm1
        """
        return self._vmov(cdg, insn, QWORD_SIZE)

    def _vmov(self, cdg, insn, data_size):
        """
        Templated handler for dword/qword mov instructions.
        """

        # op form: xmm1, rXX/mXX
        if is_xmm_reg(insn.Op1):

            # op2 -- m32/m64
            if is_mem_op(insn.Op2):
                l_reg = cdg.load_operand(1)

            # op2 -- r32/r64
            else:
                l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

            # wrap the source micro-reg as a micro-operand of the specified size
            l_mop = ida_hexrays.mop_t(l_reg, data_size)

            # op1 -- xmm1
            d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
            d_mop = ida_hexrays.mop_t(d_reg, XMM_SIZE)

            # emit the microcode for this insn
            cdg.emit(ida_hexrays.m_xdu, l_mop, NO_MOP, d_mop)

            # clear upper 128 bits of ymm1
            clear_upper(cdg, d_reg)

            return ida_hexrays.MERR_OK

        # op form: rXX/mXX, xmm1
        else:
            assert is_xmm_reg(insn.Op2)

            # op2 -- xmm1
            l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
            l_mop = ida_hexrays.mop_t(l_reg, data_size)

            # op1 -- m32/m64
            if is_mem_op(insn.Op1):
                cdg.store_operand(0, l_mop)

            # op1 -- r32/r64
            else:
                d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
                d_mop = ida_hexrays.mop_t(d_reg, data_size)
                cdg.emit(ida_hexrays.m_mov, l_mop, NO_MOP, d_mop)

                #
                # TODO: the intel manual doesn't make it entierly clear here
                # if the upper bits of a r32 operation need to be cleared ?
                # 

            return ida_hexrays.MERR_OK
        
        # failsafe
        assert "Unreachable..."
        return ida_hexrays.MERR_INSN 

    def v_mov_ps_dq(self, cdg, insn):
        """
        VMOVAPS xmm1, xmm2/m128
        VMOVAPS ymm1, ymm2/m256
        VMOVAPS xmm2/m128, xmm1
        VMOVAPS ymm2/m256, ymm1

        VMOVUPS xmm1, xmm2/m128
        VMOVUPS ymm1, ymm2/m256
        VMOVUPS xmm2/m128, xmm1
        VMOVUPS ymm2/m256, ymm1

        VMOVDQA xmm1, xmm2/m128
        VMOVDQA xmm2/m128, xmm1
        VMOVDQA ymm1, ymm2/m256
        VMOVDQA ymm2/m256, ymm1

        VMOVDQU xmm1, xmm2/m128
        VMOVDQU xmm2/m128, xmm1
        VMOVDQU ymm1, ymm2/m256
        VMOVDQU ymm2/m256, ymm1
        """

        # op form: reg, [mem]
        if is_avx_reg(insn.Op1):
            op_size = XMM_SIZE if is_xmm_reg(insn.Op1) else YMM_SIZE

            # op2 -- m128/m256
            if is_mem_op(insn.Op2):
                l_reg = cdg.load_operand(1)

            # op2 -- xmm1/ymm1
            else:
                assert is_avx_reg(insn.Op2)
                l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

            # wrap the source micro-reg as a micro-operand
            l_mop = ida_hexrays.mop_t(l_reg, op_size)

            # op1 -- xmmX/ymmX
            d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
            d_mop = ida_hexrays.mop_t(d_reg, op_size)

            # emit the microcode for this insn
            cdg.emit(ida_hexrays.m_mov, l_mop, NO_MOP, d_mop)

            # clear upper 128 bits of ymm1
            if op_size == XMM_SIZE:
                clear_upper(cdg, d_reg)

            return ida_hexrays.MERR_OK

        # op form: [mem], reg
        else:
            assert is_mem_op(insn.Op1) and is_avx_reg(insn.Op2)
            op_size = XMM_SIZE if is_xmm_reg(insn.Op2) else YMM_SIZE

            # op1 -- xmm1/ymm1
            l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
            l_mop = ida_hexrays.mop_t(l_reg, op_size)

            # [m128/m256] = xmm1/ymm1
            cdg.store_operand(0, l_mop)
            return ida_hexrays.MERR_OK

        # failsafe
        assert "Unreachable..."
        return ida_hexrays.MERR_INSN 

    #-------------------------------------------------------------------------
    # Bitwise Instructions
    #-------------------------------------------------------------------------

    def v_bitwise_ps(self, cdg, insn):
        """
        VORPS xmm1, xmm2, xmm3/m128
        VORPS ymm1, ymm2, ymm3/m256

        VXORPS xmm1, xmm2, xmm3/m128
        VXORPS ymm1, ymm2, ymm3/m256

        VANDPS xmm1, xmm2, xmm3/m128
        VANDPS ymm1, ymm2, ymm3/m256
        """
        assert is_avx_reg(insn.Op1) and is_avx_reg(insn.Op2)
        op_size = XMM_SIZE if is_xmm_reg(insn.Op1) else YMM_SIZE

        # op3 -- m128/m256
        if is_mem_op(insn.Op3):
            r_reg = cdg.load_operand(2)

        # op3 -- xmm3/ymm3
        else:
            assert is_avx_reg(insn.Op3)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

        itype2mcode = \
        {
            ida_allins.NN_vorps: ida_hexrays.m_or,
            ida_allins.NN_vandps: ida_hexrays.m_and,
            ida_allins.NN_vxorps: ida_hexrays.m_xor,
        }

        # get the hexrays microcode op to use for this instruction
        mcode_op = itype2mcode[insn.itype]

        # wrap the source micro-reg as a micro-operand
        r_mop = ida_hexrays.mop_t(r_reg, op_size)

        # op2 -- xmm2/ymm2
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
        l_mop = ida_hexrays.mop_t(l_reg, op_size)

        # op1 -- xmm1/ymm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
        d_mop = ida_hexrays.mop_t(d_reg, op_size)

        # emit the microcode for this insn
        cdg.emit(mcode_op, l_mop, r_mop, d_mop)

        # clear upper 128 bits of ymm1
        if op_size == XMM_SIZE:
            clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    #-------------------------------------------------------------------------
    # Arithmetic Instructions
    #-------------------------------------------------------------------------

    def v_math_ss(self, cdg, insn):
        """
        VADDSS    xmm1, xmm2, xmm3/m32
        VSUBSS    xmm1, xmm2, xmm3/m32
        VMULSS    xmm1, xmm2, xmm3/m32
        VDIVSS    xmm1, xmm2, xmm3/m32
        """
        return self._v_math_ss_sd(cdg, insn, FLOAT_SIZE)

    def v_math_sd(self, cdg, insn):
        """
        VADDSD    xmm1, xmm2, xmm3/m64
        VSUBSD    xmm1, xmm2, xmm3/m64
        VMULSD    xmm1, xmm2, xmm3/m64
        VDIVSD    xmm1, xmm2, xmm3/m64
        """
        return self._v_math_ss_sd(cdg, insn, DOUBLE_SIZE)

    def _v_math_ss_sd(self, cdg, insn, op_size):
        """
        Templated handler for scalar float/double math instructions.
        """
        assert is_avx_reg(insn.Op1) and is_avx_reg(insn.Op2)

        # op3 -- m32/m64
        if is_mem_op(insn.Op3):
            r_reg = cdg.load_operand(2)

        # op3 -- xmm3
        else:
            assert is_xmm_reg(insn.Op3)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

        # op2 -- xmm2
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        itype2mcode = \
        {
            ida_allins.NN_vaddss: ida_hexrays.m_fadd,
            ida_allins.NN_vaddsd: ida_hexrays.m_fadd,

            ida_allins.NN_vsubss: ida_hexrays.m_fsub,
            ida_allins.NN_vsubsd: ida_hexrays.m_fsub,

            ida_allins.NN_vmulss: ida_hexrays.m_fmul,
            ida_allins.NN_vmulsd: ida_hexrays.m_fmul,

            ida_allins.NN_vdivss: ida_hexrays.m_fdiv,
            ida_allins.NN_vdivsd: ida_hexrays.m_fdiv,
        }

        # get the hexrays microcode op to use for this instruction
        mcode_op = itype2mcode[insn.itype]
        op_dtype = ida_ua.dt_float if op_size == FLOAT_SIZE else ida_ua.dt_double

        # create a temp register to compute the final result into
        t0_result = cdg.mba.alloc_kreg(XMM_SIZE)

        # emit the microcode for this insn
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, l_reg, 0, t0_result, 0)
        cdg.emit_micro_mvm(mcode_op, op_dtype, l_reg, r_reg, t0_result, 0)
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, t0_result, 0, d_reg, 0)
        cdg.mba.free_kreg(t0_result, 16)

        # clear upper 128 bits of ymm1
        assert is_xmm_reg(insn.Op1)
        clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    def v_math_ps(self, cdg, insn):
        """
        VADDPS    xmm1, xmm2, xmm3/m128
        VADDPS    ymm1, ymm2, ymm3/m256

        VSUBPS    xmm1, xmm2, xmm3/m128
        VSUBPS    ymm1, ymm2, ymm3/m256

        VMULPS    xmm1, xmm2, xmm3/m128
        VMULPS    ymm1, ymm2, ymm3/m256

        VDIVPS    xmm1, xmm2, xmm3/m128
        VDIVPS    ymm1, ymm2, ymm3/m256
        """
        assert is_avx_reg(insn.Op1) and is_avx_reg(insn.Op2)
        op_size = XMM_SIZE if is_xmm_reg(insn.Op1) else YMM_SIZE

        # op3 -- m128/m256
        if is_mem_op(insn.Op3):
            r_reg = cdg.load_operand(2)

        # op3 -- xmm3/ymm3
        else:
            assert is_avx_reg(insn.Op3)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

        # op2 -- xmm2/ymm2
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
        
        # op1 -- xmm1/ymm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)
        d_mop = ida_hexrays.mop_t(d_reg, op_size)

        itype2name = \
        {
            ida_allins.NN_vaddps: "_mm%u_add_ps",
            ida_allins.NN_vsubps: "_mm%u_sub_ps", 
            ida_allins.NN_vmulps: "_mm%u_mul_ps",
            ida_allins.NN_vdivps: "_mm%u_div_ps",
        }

        # create the intrinsic
        bit_size = bytes2bits(op_size)
        bit_str = "256" if op_size == YMM_SIZE else ""
        intrinsic_name = itype2name[insn.itype] % bytes2bits(op_size)

        avx_intrinsic = AVXIntrinsic(cdg, intrinsic_name)
        avx_intrinsic.add_argument_reg(l_reg, "__m%u" % bit_size)
        avx_intrinsic.add_argument_reg(r_reg, "__m%u" % bit_size)
        avx_intrinsic.set_return_reg(d_reg, "__m%u" % bit_size)
        avx_intrinsic.emit()
        
        # clear upper 128 bits of ymm1
        if op_size == XMM_SIZE:
            clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    #-------------------------------------------------------------------------
    # Misc Instructions
    #-------------------------------------------------------------------------

    def vsqrtss(self, cdg, insn):
        """
        VSQRTSS xmm1, xmm2, xmm3/m32
        """
        assert is_xmm_reg(insn.Op1) and is_xmm_reg(insn.Op2)

        # op3 -- xmm3
        if is_xmm_reg(insn.Op3):
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

        # op3 -- m32
        else:
            assert is_mem_op(insn.Op3)
            r_reg = cdg.load_operand(2)

        # op2 - xmm2
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)
        
        # op1 - xmm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        # create a temp register to compute the final result into
        t0_result = cdg.mba.alloc_kreg(XMM_SIZE)

        # populate the dest reg
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, l_reg, 0, t0_result, 0)

        # mov.fpu call !fsqrt<fast:float xmm1_4.4>.4, t0_result_4.4
        avx_intrinsic = AVXIntrinsic(cdg, "fsqrt")
        avx_intrinsic.add_argument_reg_basic(r_reg, ida_typeinf.BT_FLOAT)
        avx_intrinsic.set_return_reg_basic(t0_result, ida_typeinf.BT_FLOAT)
        avx_intrinsic.emit()

        # store the fully computed result 
        cdg.emit(ida_hexrays.m_mov, XMM_SIZE, t0_result, 0, d_reg, 0)
        cdg.mba.free_kreg(t0_result, XMM_SIZE)

        # clear upper 128 bits of ymm1
        clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    def vsqrtps(self, cdg, insn):
        """
        VSQRTPS xmm1, xmm2/m128
        VSQRTPS ymm1, ymm2/m256
        """
        op_size = XMM_SIZE if is_xmm_reg(insn.Op1) else YMM_SIZE

        # op2 -- m128/m256
        if is_mem_op(insn.Op2):
            r_reg = cdg.load_operand(1)

        # op2 -- xmm2/ymm2
        else:
            assert is_avx_reg(insn.Op2)
            r_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1/ymm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        # intrinsic: __m256 _mm256_cvtepi32_ps (__m256i a)
        bit_size = bytes2bits(op_size)
        bit_str = str(bit_size) if op_size == YMM_SIZE else ""
        intrinsic_name = "_mm%s_sqrt_ps" % bit_str

        avx_intrinsic = AVXIntrinsic(cdg, intrinsic_name)
        avx_intrinsic.add_argument_reg(r_reg, "__m%u" % bit_size)
        avx_intrinsic.set_return_reg(d_reg, "__m%u" % bit_size)
        avx_intrinsic.emit()

        # clear upper 128 bits of ymm1
        if op_size == XMM_SIZE:
            clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

    def vshufps(self, cdg, insn):
        """
        VSHUFPS xmm1, xmm2, xmm3/m128, imm8
        VSHUFPS ymm1, ymm2, ymm3/m256, imm8
        """
        op_size = XMM_SIZE if is_xmm_reg(insn.Op1) else YMM_SIZE

        # op4 -- imm8
        assert insn.Op4.type == ida_ua.o_imm
        mask_value = insn.Op4.value
        
        # op3 -- m128/m256
        if is_mem_op(insn.Op3):
            r_reg = cdg.load_operand(2)

        # op3 -- xmm3/ymm3
        else:
            assert is_avx_reg(insn.Op3)
            r_reg = ida_hexrays.reg2mreg(insn.Op3.reg)

        # op2 -- xmm2/ymm2
        l_reg = ida_hexrays.reg2mreg(insn.Op2.reg)

        # op1 -- xmm1/ymm1
        d_reg = ida_hexrays.reg2mreg(insn.Op1.reg)

        # 
        # intrinsics:
        #   __m128 _mm_shuffle_ps (__m128 a, __m128 b, unsigned int imm8)
        #   __m256 _mm256_shuffle_ps (__m256 a, __m256 b, const int imm8)
        #

        bit_size = bytes2bits(op_size)
        bit_str = str(bit_size) if op_size == YMM_SIZE else ""
        intrinsic_name = "_mm%s_shuffle_ps" % bit_str

        avx_intrinsic = AVXIntrinsic(cdg, intrinsic_name)
        avx_intrinsic.add_argument_reg(l_reg, "__m%u" % bit_size)
        avx_intrinsic.add_argument_reg(r_reg, "__m%u" % bit_size)
        avx_intrinsic.add_argument_imm(mask_value, ida_typeinf.BT_INT8)
        avx_intrinsic.set_return_reg(d_reg, "__m%u" % bit_size)
        avx_intrinsic.emit()

        # clear upper 128 bits of ymm1
        if op_size == XMM_SIZE:
            clear_upper(cdg, d_reg)

        return ida_hexrays.MERR_OK

#-----------------------------------------------------------------------------
# Plugin
#-----------------------------------------------------------------------------

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

class MicroAVX(ida_idaapi.plugin_t):
    """
    The IDA plugin stub for MicroAVX.
    """

    flags = ida_idaapi.PLUGIN_PROC | ida_idaapi.PLUGIN_HIDE
    comment = "AVX support for the Hex-Rays x64 Decompiler"
    help = ""
    wanted_name = "MicroAVX"
    wanted_hotkey = ""
    loaded = False

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

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

        # only bother to load the plugin for relevant sessions
        if not is_amd64_idb():
            return ida_idaapi.PLUGIN_SKIP

        # ensure the x64 decompiler is loaded
        ida_loader.load_plugin("hexx64")
        assert ida_hexrays.init_hexrays_plugin(), "Missing Hexx64 Decompiler..."

        # initialize the AVX lifter 
        self.avx_lifter = AVXLifter()
        self.avx_lifter.install()
        sys.modules["__main__"].lifter = self.avx_lifter

        # mark the plugin as loaded
        self.loaded = True
        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        ida_kernwin.warning("%s cannot be run as a script in IDA." % self.wanted_name)

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

        # hex-rays automatically cleans up decompiler hooks, so not much to do here...
        self.avx_lifter = None


repository: Ph0en1x-XMU/Ph0en1x-Team
file: Reverse/ida/IDA Pro 7.5.201028 (x86,x64,ARM,ARM64,PPC,PPC64,MIPS)/python/examples/hexrays/vds6.py
wanted_name: Hex-Rays space remover (IDAPython)
comment: Sample plugin6 for Hex-Rays decompiler
created_at: 2022-06-02 11:06:16+00:00
pushed_at: 2022-06-02 11:11:40+00:00
url:
https://github.com/Ph0en1x-XMU/Ph0en1x-Team/blob/a8d80aaea8227e418f5826bf05ef69a1bc8d84f1/Reverse/ida/IDA%20Pro%207.5.201028%20(x86,x64,ARM,ARM64,PPC,PPC64,MIPS)/python/examples/hexrays/vds6.py

content:
"""
This is a crude (and not very pythonic) reimplementation of the example
hexrays plugin 'hexrays_sample6.cpp', shipped with the Hex-Rays decompiler.

It modifies the decompilation output: removes some space characters.
"""
from __future__ import print_function

import idautils
import idc
import ida_idaapi
import ida_hexrays
import ida_lines

def dbg(msg):
    #print(msg)
    pass

def is_cident_char(c):
    return c.isalnum() or c == '_'

def my_tag_skipcodes(l, storage):
    n = ida_lines.tag_skipcodes(l)
    dbg("Skipping %d chars ('%s')" % (n, l[0:n]))
    storage.append(l[0:n])
    return l[n:]

def remove_spaces(sl):
    dbg("*" * 80)
    l = sl.line
    out = []

    def push(c):
        dbg("Appending '%s'" % c)
        out.append(c)

    # skip initial spaces, do not compress them
    while True:
        l = my_tag_skipcodes(l, out)
        if not l:
            break
        c = l[0]
        if not c.isspace():
            break
        push(c)
        l = l[1:]

    # remove all spaces except in string and char constants
    delim = None # if not None, then we are skipping until 'delim'
    last = None # last seen character
    while True:
        # go until comments
        if l.startswith("//"):
            push(l)
            break
        dbg("-" * 60)
        nchars = ida_lines.tag_advance(l, 1)
        push(l[0:nchars])
        l = l[nchars:]
        l = my_tag_skipcodes(l, out)
        if not l:
            break
        c = l[0]
        dbg("c: '%s', last: '%s', l: '%s'" % (c, last, l))
        if delim:
            # we're inside a literal.
            if c == delim:
                delim = None # literal ended
        elif c == '"' or c == "'":
            delim = c # string/char literal started
        elif c.isspace():
            end = l.lstrip()
            nptr = my_tag_skipcodes(end, out)
            dbg("end: '%s', nptr: '%s'" % (end, nptr))
            # do not concatenate idents
            if not is_cident_char(last) or not is_cident_char(nptr[0]):
                l = end
                c = l[0] if l else ''
                dbg("new l: '%s'" % l)
        last = l[0] if l else ''

    sl.line = "".join(out)


class vds6_hooks_t(ida_hexrays.Hexrays_Hooks):
    def func_printed(self, cfunc):
        for sl in cfunc.get_pseudocode():
            remove_spaces(sl);
        return 0

# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "Hex-Rays space remover (IDAPython)"
    wanted_hotkey = ""
    comment = "Sample plugin6 for Hex-Rays decompiler"
    help = ""
    def init(self):
        if ida_hexrays.init_hexrays_plugin():
            self.vds6_hooks = vds6_hooks_t()
            self.vds6_hooks.hook()
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
        self.vds6_hooks.unhook()
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return my_plugin_t()


repository: REMnux/distro
file: ppasrc/ghidra/ghidra-10.1-bionic/ghidra/Extensions/IDAPro/Python/6xx/plugins/xmlexp.py
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2014-09-12 19:41:54+00:00
pushed_at: 2025-02-26 18:45:43+00:00
description:
This repository contains supplemental files for the REMnux distro and the source files for the Debian packages that the distro installs from the REMnux package repository on Launchpad.

url:
https://github.com/REMnux/distro/blob/9789bbc22b57362bc26e7025059bf72ac2f7bcd9/ppasrc/ghidra/ghidra-10.1-bionic/ghidra/Extensions/IDAPro/Python/6xx/plugins/xmlexp.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.
"""

import idaapi
import idautils
import idc
import datetime
import sys
import time


XML_EXPORTER_VERSION = "4.1.2"
BASELINE_IDA_VERSION = 620
IDA_SDK_VERSION = idaapi.IDA_SDK_VERSION
BADADDR = idaapi.BADADDR


class XmlExporterPlugin(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 IDA_SDK_VERSION < BASELINE_IDA_VERSION:
            idaapi.msg('\nXML Exporter plugin (xmlexp.py) not supported ' +
                        'by this version of IDA\n')
            return idaapi.PLUGIN_SKIP
        return idaapi.PLUGIN_OK


    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.
        """
        xml = XmlExporter(arg)
        try:
            try:
                xml.export_xml()
            except Cancelled:
                idaapi.hide_wait_box()
                msg = "XML Export cancelled!"
                print "\n" + msg
                idaapi.warning(msg)
            except:
                idaapi.hide_wait_box()
                msg = "***** Exception occurred: XML Exporter failed! *****"
                print "\n" + msg + "\n", sys.exc_type, sys.exc_value
                idaapi.warning(msg)
        finally:
            xml.cleanup()
            idaapi.setStat(xml.state)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlExporterPlugin()


class Cancelled(Exception):
    pass


class FileError(Exception):
    pass


class XmlExporter:
    """
    XML Exporter contains methods to export an IDA database as a
        XML PROGRAM document.
    """
    def __init__(self, arg):
        """
        Initializes the XmlExporter attributes

        Args:
            arg: Integer, non-zero value enables auto-run feature for
                IDA batch (no gui) processing mode. Default is 0.
        """
        self.debug = False
        self.autorun = False
        if arg != 0:
            self.autorun = True
        self.state = idaapi.setStat(idaapi.st_Work)
        self.indent_level = 0
        self.seg_addr = False
        self.has_overlays = False
        self.hexrays = False
        self.options = None
        self.xmlfile = 0
        self.elements = {}
        self.counters = []
        self.tags = []
        
        # initialize class variables from database
        self.inf = idaapi.get_inf_structure()
        self.min_ea = self.inf.minEA
        self.max_ea = self.inf.maxEA
        self.cbsize = (idaapi.ph_get_cnbits()+7)/8
        self.processor = str.upper(idaapi.get_idp_name())
        self.batch = idaapi.cvar.batch


    def export_xml(self):
        """
        Exports the IDA database to a XML PROGRAM document file.
        """
        self.check_and_load_decompiler()
        self.display_xml_exporter_version()
        self.display_database_info()
        
        self.get_options()
    
        if (self.autorun == True):
            (self.filename, ext) = os.path.splitext(idaapi.cvar.database_idb)
            self.filename += ".xml"
        else:
            self.filename=idaapi.askfile_c(1, "*.xml",
                                           "Enter name of export xml file:")
            
        if self.filename == None or len(self.filename) == 0:
            raise Cancelled
        self.xmlfile = self.open_file(self.filename, "w")
        
        idaapi.show_wait_box("Exporting XML <PROGRAM> document ....")
        idaapi.msg("\n------------------------------------------------" +
                   "-----------")
        idaapi.msg("\nExporting XML <PROGRAM> document ....")
        begin = time.clock()
        
        self.write_xml_declaration()
        self.export_program()
        
        # export database items based on options
        if (self.options.DataTypes.checked         == True or
            self.options.DataDefinitions.checked   == True or 
            self.options.Functions.checked         == True):
            self.export_datatypes()
        if (self.options.MemorySections.checked    == True or
            self.options.MemoryContent.checked     == True):
            self.export_memory_map()
        if (self.options.RegisterValues.checked    == True):
            self.export_register_values()   
        if (self.options.CodeBlocks.checked        == True):
            self.export_code()  
        if (self.options.DataDefinitions.checked   == True):
            self.export_data()  
        if (self.options.Comments.checked          == True):
            self.export_comments()
            self.export_bookmarks()     
        if (self.options.EntryPoints.checked       == True):
            self.export_program_entry_points()  
        if (self.options.Symbols.checked           == True):
            self.export_symbol_table()  
        if (self.options.Functions.checked         == True):
            self.export_functions() 
        if (self.options.MemoryReferences.checked  == True or 
            self.options.StackReferences.checked   == True or
            self.options.Manual.checked            == True or
            self.options.DataTypes.checked         == True):
            self.export_markup()    
        self.end_element(PROGRAM)
        
        idaapi.msg('\n%35s' % 'Total ')
        self.display_cpu_time(begin)
        idaapi.hide_wait_box()  
        self.display_summary()


    def check_and_load_decompiler(self):
        """
        Checks for the presence of a decompiler plugin for the database.
        
        Note: The decompiler must be loaded by the XML Exporter plugin
            if it is running in batch mode. IDA will load the decompiler
            plugin automatically if not in batch mode.
        
        Note: There was no support for decompiler plugins in IDAPython until
            IDA 6.6, so skip if this is an older version.
        
        Note: Currently the 4 decompiler plugins for the  x86, x64,
            ARM32, and ARM64 are supported.
        """
        if self.batch == 0 or IDA_SDK_VERSION < 660:
            return
        plugin = ''
        if self.processor == 'PC':
            if self.inf.is_64bit():
                plugin = "hexx64"
            elif self.inf.is_32bit():
                plugin = 'hexrays'
        elif self.processor == 'ARM':
            if self.inf.is_64bit():
                plugin = "hexarm64"
            elif self.inf.is_32bit():
                plugin = "hexarm"
        if len(plugin) > 0:
            try:
                idaapi.load_plugin(plugin)
                self.hexrays = idaapi.init_hexrays_plugin()
            except:
                return
    

    def check_char(self, ch):
        """
        Replaces a special XML character with an entity string.
        
        Args:
            ch: String containing the character to check.
            
        Returns:
            String containing either the character or the entity
            substition string.
        """
        if ((ord(ch) < 0x20) and (ord(ch) != 0x09 and
             ord(ch) != 0x0A and ord(ch) != 0x0D)): return ''
        elif ch == '&' :  return '&amp;'
        elif ch == '<' :  return "&lt;"
        elif ch == '>' :  return "&gt;"
        elif ch == '\'' : return "&apos;"
        elif ch == '"' :  return "&quot;"
        elif ch == '\x7F': return ''
        elif ord(ch) > 0x7F: return '&#x' + format(ord(ch),"x") + ";"
        return ch
    

    def check_for_entities(self, text):
        """
        Checks all characters in a string for special XML characters.
        
        Args:
            text: String to check for special XML characters.
            
        Returns:
            String containing original string with substitutions for
                any special XML characters.
        """
        new = ''
        for c in text:
            new += self.check_char(c)
        return new
    

    def check_if_seg_contents(self, seg):
        """
        Determines if any address in a segment contains a value.
        
        Args:
            seg: IDA segment object
            
        Returns:
            True if any address in a segment contains a value.
            False if no address in a segment contains a value.
        """
        for addr in idautils.Heads(seg.startEA, seg.endEA):
            if idaapi.hasValue(idaapi.getFlags(addr)) == True:
                return True
        return False


    def check_stack_frame(self, sframe):
        """
        Determines if stack frame contains any parameters or local variables.
        
        Args:
            sframe: IDA stack frame for a function.
            
        Returns:
            True if stack frame has parameters or local variables.
            False if stack frame has no parameters or local variables.
        """
        n = sframe.memqty
        for i in range(n):
            member = sframe.get_member(i)
            if member == None:
                continue
            mname = idaapi.get_member_name(member.id)
            if mname != None and len(mname) > 0: 
                if mname != " s" and mname != " r":
                    return True
        return False


    def cleanup(self):
        """
        Frees memory and closes message box and XML file at termination.
        """
        if self.options != None:
            self.options.Free()
        idaapi.hide_wait_box()
        self.close_xmlfile()


    def close_binfile(self):
        """
        Closes the binary data file for the XML Exporter.
        """
        if self.binfile != 0:
            self.binfile.close()
            self.binfile = 0


    def close_tag(self, has_contents=False):
        """
        Closes the start tag for an XML element.
        
        Args:
            has_contents: Boolean indicating if the element has
            sub-elements or text.
        """
        if has_contents:
            self.write_to_xmlfile(">")
            self.indent_level += 1
        else:
            self.write_to_xmlfile(" />")


    def close_xmlfile(self):
        """
        Closes the XML data file for the XML Exporter.
        """
        if self.xmlfile != 0:
            self.xmlfile.close()
            self.xmlfile = 0


    def dbg(self, message):
        """
        Outputs debug message if debug flag is enabled.
        
        Args:
            message: String containing the debug message.
        """
        if (self.debug == True):
            idaapi.msg(message)


    def display_cpu_time(self, start):
        """
        Displays the elapsed CPU time since the start time.
        
        Args:
            start: Floating-point value representing start time in seconds.
        """
        idaapi.msg('CPU time: %6.4f' % (time.clock() - start))
        

    def display_database_info(self):
        """
        Displays information about the database in IDA output window.
        """
        idaapi.msg('\nProcessor Module: ' + self.processor)


    def display_message(self, message):
        """
        Displays an indented message to IDA output window.
        
        Args:
            message: String containing message to display.
        """
        line = "    " + message
        idaapi.msg('\n%-35s' % line)
        

    def display_summary(self):
        """
        Displays summary of exported PROGRAM document in IDA output window.
        """
        fileline = '\nFile: %s' % self.filename
        summary  = "\n--------------------------------------"
        total = 0
        tags = self.tags[:]
        for tag in tags:
            count = self.counters[self.elements[tag]]
            summary += "\n%-22s %8d" % (tag, count)
            total += count
        summary += ("\n%-22s %8d" % ("Total XML Elements:",total))
        idaapi.msg(summary)
        idaapi.msg("\n\nDatabase exported to: %s" % self.filename)
        idaapi.msg("\n--------------------------------------------" +
                   "---------------")
        if self.autorun == False:
            frmt  = "TITLE XML Export Successful!\n"
            frmt += "ICON INFO\n"
            frmt += "AUTOHIDE NONE\n"
            frmt += "HIDECANCEL\n"
            details = '\nSee output window for details...'
            idaapi.info("%s" % (frmt + fileline + details))


    def display_xml_exporter_version(self):
        """
        Displays XML Exporter plugin version info in IDA output window.
        """
        plugin = idaapi.idadir(idaapi.PLG_SUBDIR) + '/xmlexp.py'
        plugintime = time.localtime(os.path.getmtime(plugin))
        # ts = time.strftime('%Y-%m-%d %H:%M:%S', plugintime)
        ts = time.strftime('%b %d %Y %H:%M:%S', plugintime)
        version = "\nXML Exporter Version " + XML_EXPORTER_VERSION
        version += " : SDK " + str(IDA_SDK_VERSION)
        version += " : Python : "+ ts + '\n'
        idaapi.msg(version)
    

    def end_element(self, tag, newline=True):
        """
        Writes the element end tag to the XML file.
        
        Args:
            tag: String containing the element name.
            newline: Boolean indicating if end tag should go on new line.
        """
        self.indent_level -= 1
        if newline:
            start = '\n' + ("    " * self.indent_level)
        else:
            start = ''
        self.write_to_xmlfile(start  + "</" + tag + ">")


    '''
    # BIT_MASK not currently supported for ENUM
    def export_bitmask(self, eid, mask):
        """
        Exports an enum bitmask member as BIT_MASK element.
        
        Args:
            eid: Integer representing the IDA enum id
            mask: Integer representing the IDA enum mask value
        """
        name = idaapi.get_bmask_name(eid, mask)
        if name == None:
            return
        self.start_element(BIT_MASK)
        self.write_attribute(NAME, name)
        self.write_numeric_attribute(VALUE, mask)
        regcmt = idaapi.get_bmask_cmt(eid, mask, False)
        rptcmt = idaapi.get_bmask_cmt(eid, mask, True)
        has_comment =  regcmt != None or rptcmt != None
        self.close_tag(has_comment)
        if regcmt != None and len(regcmt) > 0:
            self.export_regular_cmt(regcmt)
        if rptcmt != None and len(rptcmt) > 0:
            self.export_repeatable_cmt(rptcmt)
        if (has_comment):
            self.end_element(BIT_MASK)
    '''


    def export_bookmarks(self):
        """
        Exports marked location descriptions as BOOKMARK elements.
        """
        found = False
        timer = time.clock()
        if IDA_SDK_VERSION > 690:
            import ida_moves
            import ida_pro
        for slot in range(1,1025):
            if IDA_SDK_VERSION <= 690:
                address = idc.GetMarkedPos(slot)
                description = idc.GetMarkComment(slot)
            else:
                curloc = ida_moves.curloc()
                intp = ida_pro.int_pointer()
                intp.assign(slot)
                address = curloc.markedpos(intp)
                description = curloc.markdesc(slot)
            if address == BADADDR:
                continue
            if description == None:
                continue
            if found == False:
                found = True
                self.update_status(BOOKMARKS)
                self.start_element(BOOKMARKS, True)
            self.start_element(BOOKMARK)
            self.write_address_attribute(ADDRESS, address)
            self.write_attribute(DESCRIPTION, description)
            self.close_tag()
        if found:
            self.end_element(BOOKMARKS)
            self.display_cpu_time(timer)


    def export_c_comments(self):
        """
        Exports block and end-of-line comments entered in the decompiler
        interface.
        """
        if IDA_SDK_VERSION < 660 or self.hexrays == False:
            return
        functions = idautils.Functions()
        if functions == None:
            return
        for addr in functions:
            try:
                if idaapi.is_spec_ea(addr):
                    continue
                ccmts = idaapi.restore_user_cmts(addr)
                if ccmts == None:
                    continue
                p = idaapi.user_cmts_begin(ccmts)
                while p != idaapi.user_cmts_end(ccmts):
                    cmk = idaapi.user_cmts_first(p)
                    cmv = idaapi.user_cmts_second(p)
                    if cmk.itp < (idaapi.ITP_COLON+1):
                        self.export_comment(cmk.ea, "end-of-line", cmv.c_str())
                    else:
                        self.export_comment(cmk.ea, "pre", cmv.c_str())
                    p=idaapi.user_cmts_next(p)
                idaapi.user_cmts_free(ccmts)
            except:
                continue


    def export_code(self):
        """
        Exports the address ranges of code sequences as CODE_BLOCK(s)
        with START and END address attributes.
        """
        addr = self.min_ea
        if idaapi.isCode(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.isCode)
        if (addr == BADADDR):
            return
        self.update_status(CODE)
        timer = time.clock()
        data = idaapi.nextthat(addr, self.inf.maxEA, isData)
        unknown = idaapi.next_unknown(addr, self.inf.maxEA)
        self.start_element(CODE, True)
        while (addr != BADADDR):
            start = addr
            end = min(data, unknown)
            if (end == BADADDR):
                if (idaapi.getseg(start).endEA < self.inf.maxEA):
                    codeend = idaapi.getseg(start).endEA - 1
                    addr = idaapi.getseg(idaapi.nextaddr(codeend)).startEA
                    if idaapi.isCode(idaapi.getFlags(addr)) == False:
                        addr = idaapi.nextthat(addr, self.max_ea,
                                               idaapi.isCode)
                else:
                    codeend = self.max_ea - 1
                    addr = BADADDR
            else:
                if (idaapi.getseg(start).endEA < end):
                    codeend = idaapi.getseg(start).endEA - 1
                    addr = idaapi.getseg(idaapi.nextaddr(codeend)).startEA
                    if idaapi.isCode(idaapi.getFlags(addr)) == False:
                        addr = idaapi.nextthat(addr, self.max_ea,
                                               idaapi.isCode)
                else:
                    codeend = idaapi.get_item_end(idaapi.prevthat(end,
                                                start, idaapi.isCode)) - 1
                    addr = idaapi.nextthat(end, self.max_ea,
                                           idaapi.isCode)
                if (data < addr):
                    data = idaapi.nextthat(addr, self.max_ea,
                                           idaapi.isData)
                if (unknown < addr):
                    unknown = idaapi.next_unknown(addr, self.max_ea)
            self.start_element(CODE_BLOCK)
            self.write_address_attribute(START, start)
            self.write_address_attribute(END, codeend)
            self.close_tag()
        self.end_element(CODE)
        self.display_cpu_time(timer)


    def export_comment(self, addr, cmt_type, cmt):
        """
        Exports a <COMMENT> element with ADDRESS and TYPE attributes.
        The comment is exported as the element text (parsed character data).
        
        Args:
            addr: Integers representing address of comment.
            cmt_type: String indicating the comment type.
            cmt: String containing the comment.
        """
        self.start_element(COMMENT)
        self.write_address_attribute(ADDRESS, addr)
        self.write_attribute(TYPE, cmt_type)
        self.close_tag(True)
        # tag_remove seems to be losing last character
        # work around is to add a space
        cmt_text = idaapi.tag_remove(cmt + ' ')
        self.write_text(cmt_text)
        self.end_element(COMMENT, False)


    def export_comments(self):
        """
        Exports all comments in the IDA database as <COMMENT> elements.
        """
        addr = self.min_ea
        if idaapi.has_cmt(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.has_cmt)
        if (addr == BADADDR):
            return
        self.update_status(COMMENTS)
        timer = time.clock()
        self.start_element(COMMENTS, True)
        while (addr != BADADDR):
            cmt = idaapi.get_cmt(addr, False)
            if (cmt != None):
                self.export_comment(addr, "end-of-line", cmt)
            cmt = idaapi.get_cmt(addr, True)
            if (cmt != None):
                self.export_comment(addr, "repeatable", cmt)
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.has_cmt)
        addr = self.min_ea
        if idaapi.hasExtra(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.hasExtra)
        while (addr != BADADDR):
            if IDA_SDK_VERSION < 640:
                extra = idaapi.ExtraGet(addr, idaapi.E_PREV)
            else:
                extra = idaapi.get_extra_cmt(addr, idaapi.E_PREV)
            if (extra != None):
                self.export_extra_comment(addr, "pre", idaapi.E_PREV)
            if IDA_SDK_VERSION < 640:
                extra = idaapi.ExtraGet(addr, idaapi.E_NEXT)
            else:
                extra = idaapi.get_extra_cmt(addr, idaapi.E_NEXT)
            if (extra != None):
                self.export_extra_comment(addr, "post", idaapi.E_NEXT)
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.hasExtra)
        self.export_c_comments()
        self.end_element(COMMENTS)
        self.display_cpu_time(timer)


    def export_data(self):
        """
        Exports the data items in the database as <DEFINED_DATA> elements.
        """
        addr = self.min_ea
        if idaapi.isData(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.isData)
        if (addr == BADADDR):
            return
        timer = time.clock()
        self.update_status(DATA)
        self.start_element(DATA, True)
        while (addr != BADADDR):
            f = idaapi.getFlags(addr)
            if idaapi.isAlign(f) == True:
                addr = idaapi.nextthat(addr, self.max_ea, idaapi.isData)
                continue
            dtype = self.get_datatype(addr)
            size = idaapi.get_item_size(addr)
            ti = idaapi.opinfo_t()
            if IDA_SDK_VERSION < 640:
                msize = idaapi.get_data_elsize(addr, f, ti)
            else:
                msize = idaapi.get_data_type_size(f, ti)
            if idaapi.isStruct(f) == True:
                s = idaapi.get_struc_id(dtype)
                msize = idaapi.get_struc_size(s)
                if msize == 0:
                    msize = 1
            if idaapi.isASCII(f) == False and size != msize:
                dtype = "%s[%d]" % (dtype, size/msize)
            self.start_element(DEFINED_DATA)
            self.write_address_attribute(ADDRESS, addr)
            self.write_attribute(DATATYPE, dtype)
            self.write_numeric_attribute(SIZE, size*self.cbsize)
            #TODO consider using GetTrueNameEx and Demangle
            demangled = idaapi.get_demangled_name(BADADDR, addr,
                            DEMANGLED_TYPEINFO, self.inf.demnames, True)
            outbuf = ''
            if IDA_SDK_VERSION < 660:
                outbuf = idaapi.print_type(addr, False)
            else:
                outbuf = idaapi.print_type(addr, outbuf)
            if demangled == "'string'":
                demangled == None
            has_typeinfo = demangled != None or (outbuf != None and
                                                len(outbuf) > 0)
            #TODO export_data: add DISPLAY_SETTINGS
            self.close_tag(has_typeinfo)
            if has_typeinfo == True:
                if demangled != None:
                    self.export_typeinfo_cmt(demangled)
                else:
                    #TODO export_data - check this
                    self.export_typeinfo_cmt(outbuf[:-1])
                self.end_element(DEFINED_DATA)
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.isData)
        self.end_element(DATA)
        self.display_cpu_time(timer)
        

    def export_datatypes(self):
        """
        Exports the structures and enums in IDA database.
        """
        # skip if no structures/unions to export
        if idaapi.get_struc_qty() == 0: return
        self.update_status(DATATYPES)
        timer = time.clock()
        self.start_element(DATATYPES, True)
        self.export_structures()
        self.export_enums()
        self.end_element(DATATYPES)
        self.display_cpu_time(timer)


    def export_enum_member(self, cid, bf, mask, radix, signness):
        """
        Exports a member of an enum.

        This function can only be called by IDA versions newer than 6.3 
        
        Args:
            cid: Integer representing id of enum member
            bf: Boolean indicates if a bitfield
            mask: Integer representing bitmask if bitfield
            radix: Integer representing numeric display format
            signness: Boolean indicating if signed value 
        """
        if IDA_SDK_VERSION < 640:
            return
        cname = idaapi.get_const_name(cid)
        if cname == None or len(cname) == 0:
            return
        regcmt = idaapi.get_const_cmt(cid, False)
        rptcmt = idaapi.get_const_cmt(cid, True)
        has_comment =  regcmt != None or rptcmt != None
        self.start_element(ENUM_ENTRY)
        self.write_attribute(NAME, cname)
        value = idaapi.get_const_value(cid)
        self.write_numeric_attribute(VALUE, value, radix, signness)
        # BIT_MASK attribute not currently supported for ENUM_ENTRY
        #if bf == True:
        #    self.write_numeric_attribute(BIT_MASK, mask)
        self.close_tag(has_comment)
        if regcmt != None and len(regcmt) > 0:
            self.export_regular_cmt(regcmt)
        if rptcmt != None and len(rptcmt) > 0:
            self.export_repeatable_cmt(rptcmt)
        if (has_comment):
            self.end_element(ENUM_ENTRY)


    def export_enum_member_legacy(self, cid, bf, mask, radix, signness):
        """
        Exports a member of an enum.
        
        This function can only be called by IDA versions older than 6.3 

        Args:
            cid: Integer representing id of enum member
            bf: Boolean indicates if a bitfield
            mask: Integer representing bitmask if bitfield
            radix: Integer representing numeric display format
            signness: Boolean indicating if signed value 
        """
        if IDA_SDK_VERSION > 630:
            return
        cname = idaapi.get_enum_member_name(cid)
        if cname == None or len(cname) == 0:
            return
        regcmt = idaapi.get_enum_member_cmt(cid, False)
        rptcmt = idaapi.get_enum_member_cmt(cid, True)
        has_comment =  regcmt != None or rptcmt != None
        self.start_element(ENUM_ENTRY)
        self.write_attribute(NAME, cname)
        value = idaapi.get_enum_member_value(cid)
        self.write_numeric_attribute(VALUE, value, radix, signness)
        # BIT_MASK attribute not currently supported for ENUM_ENTRY
        #if bf == True:
        #    self.write_numeric_attribute(BIT_MASK, mask)
        self.close_tag(has_comment)
        if regcmt != None and len(regcmt) > 0:
            self.export_regular_cmt(regcmt)
        if rptcmt != None and len(rptcmt) > 0:
            self.export_repeatable_cmt(rptcmt)
        if (has_comment):
            self.end_element(ENUM_ENTRY)


    def export_enum_members(self, eid, bf, eflags):
        """
        Exports the members of an enum.

        This function can only be called by IDA versions newer than 6.3 
        
        Args:
            eid: Integer representing id of enum
            bf: Boolean indicates if a bitfield
            eflags: Integer representing the enum flags
        """
        if IDA_SDK_VERSION < 640:
            return
        mask=0xFFFFFFFF
        if bf == True:
            mask = idaapi.get_first_bmask(eid)
        first = True
        for n in range(idaapi.get_enum_size(eid)):
            if (first == True):
                value = idaapi.get_first_const(eid, mask)
                first = False
            else:
                value = idaapi.get_next_const(eid, value, mask)
            (cid, serial) = idaapi.get_first_serial_const(eid, value, mask)
            main_cid = cid
            while cid != idaapi.BADNODE:
                self.export_enum_member(cid, bf, mask,
                                   idaapi.getRadix(eflags, 0),
                                   self.is_signed_data(eflags))
                last_value = idaapi.get_last_const(eid, mask)
                if value == last_value:
                    # BIT_MASK not currently supported for ENUM
                    #self.export_bitmask(eid, mask)
                    mask = idaapi.get_next_bmask(eid, mask)
                    first = True
                (cid, serial) = idaapi.get_next_serial_const(main_cid)


    def export_enum_members_legacy(self, eid, bf, eflags):
        """
        Exports the members of an enum.
        
        This function can only be called by IDA versions older than 6.3 
        
        Args:
            eid: Integer representing id of enum
            bf: Boolean indicates if a bitfield
            eflags: Integer representing the enum flags
        """
        if IDA_SDK_VERSION > 630:
            return
        mask=0xFFFFFFFF
        if bf == True:
            mask = idaapi.get_first_bmask(eid)
        first = True
        for n in range(idaapi.get_enum_size(eid)):
            if (first == True):
                value = idaapi.get_first_enum_member(eid, mask)
                first = False
            else:
                value = idaapi.get_next_enum_member(eid, value, mask)
            (cid, serial) = idaapi.get_first_serial_enum_member(eid, value, mask)
            main_cid = cid
            while cid != idaapi.BADNODE:
                self.export_enum_member_legacy(cid, bf, mask,
                                   idaapi.getRadix(eflags, 0),
                                   self.is_signed_data(eflags))
                last_value = idaapi.get_last_enum_member(eid, mask)
                if value == last_value:
                    # BIT_MASK not currently supported for ENUM
                    #self.export_bitmask(eid, mask)
                    mask = idaapi.get_next_bmask(eid, mask)
                    first = True
                (cid, serial) = idaapi.get_next_serial_enum_member(main_cid)


    def export_enum_reference(self, addr, op):
        """
        Exports the enum reference for an operand at an address.
        
        Args:
            addr: Integer representing the instruction address.
            op: Integer representing the operand index (0-based)
        """
        (eid, serial) = idaapi.get_enum_id(addr, op)
        idaapi.decode_insn(addr)
        value = idaapi.cmd.Operands[op].value
        cid = idaapi.BADNODE
        last = idaapi.get_last_bmask(eid)
        if idaapi.is_bf(eid) == True:
            last = idaapi.get_last_bmask(eid)
            mask = idaapi.get_first_bmask(eid)
            while  (cid == idaapi.BADNODE):
                if IDA_SDK_VERSION < 640:
                    cid = idaapi.get_enum_member(eid, (value & mask), 0, mask)
                else:
                    cid = idaapi.get_const(eid, (value & mask), 0, mask)
                if cid != idaapi.BADNODE or mask == last:
                    break
                mask = idaapi.get_next_bmask(eid, mask)
        else:
            if IDA_SDK_VERSION < 640:
                cid = idaapi.get_enum_member(eid, value, 0, last)
            else:
               cid = idaapi.get_const(eid, value, 0, last)
        if (cid == idaapi.BADNODE):
            return
        self.start_element(EQUATE_REFERENCE)
        self.write_address_attribute(ADDRESS, addr)
        self.write_numeric_attribute(OPERAND_INDEX, op, 10)
        if IDA_SDK_VERSION < 640:
            self.write_numeric_attribute(VALUE, idaapi.get_enum_member_value(cid))
            cname = idaapi.get_enum_member_name(cid)
        else:
            self.write_numeric_attribute(VALUE, idaapi.get_const_value(cid))
            cname = idaapi.get_const_name(cid)
        if cname != None and len(cname) > 0:
            self.write_attribute(NAME, cname)
        # BIT_MASK feature not currently supported
        #if idaapi.is_bf(eid) == True:
        #    self.write_numeric_attribute("BIT_MASK", mask);
        self.close_tag()
        

    def export_enum_references(self, addr):
        """
        Finds and exports enum references at an address.
        
        Args:
            addr: Integer representing the instruction address.
        """
        f = idaapi.getFlags(addr)
        for op in range(2):
            if idaapi.isEnum(f, op) == True:
                self.export_enum_reference(addr, op)
                

    def export_enums(self):
        """
        Exports enumerations.
        """
        num_enums = idaapi.get_enum_qty()
        if (num_enums == 0):
            return
        for i in range(num_enums):
            self.start_element(ENUM)
            eid = idaapi.getn_enum(i)
            ename = idaapi.get_enum_name(eid)
            if (ename == None or len(ename) == 0):
                continue
            self.write_attribute(NAME, ename)
            ewidth = idaapi.get_enum_width(eid)
            if ewidth != 0 and ewidth <= 7:
                self.write_numeric_attribute(SIZE, 1 << (ewidth-1), 10)
            eflags = idaapi.get_enum_flag(eid)
            bf = idaapi.is_bf(eid)
            # BIT_FIELD attribute not supported for ENUM
            #if bf == True:
            #    self.write_attribute(BIT_FIELD, "yes")
            regcmt = idaapi.get_enum_cmt(eid, False)
            rptcmt = idaapi.get_enum_cmt(eid, True)
            has_children = ((idaapi.get_enum_size(eid) > 0) or
                            (regcmt != None) or (rptcmt != None) or
                            (idaapi.getRadix(eflags, 0) != 16) or
                            (self.is_signed_data(eflags) == True))
            self.close_tag(has_children)
            if (idaapi.getRadix(eflags, 0) != 16 or
                self.is_signed_data(eflags) == True):
                self.start_element(DISPLAY_SETTINGS)
                if idaapi.getRadix(eflags, 0) != 16:
                    self.write_attribute(FORMAT, self.get_format(eflags))
                if self.is_signed_data(eflags) == True:
                    self.write_attribute(SIGNED, "yes")
                self.close_tag()
            if regcmt != None:
                self.export_regular_cmt(regcmt)
            if rptcmt != None:
                self.export_repeatable_cmt(rptcmt)
            if IDA_SDK_VERSION < 640:
                self.export_enum_members_legacy(eid, bf, eflags)
            else:
                self.export_enum_members(eid, bf, eflags)
            if (has_children):
                self.end_element(ENUM)


    def export_extra_comment(self, addr, cmt_type, extra):
        """
        Exports pre- and post- comments for an address.
        
        Args:
            addr: Integer representing the instruction address.
            cmt_type: String indicating comment type
            extra: Integer representing extra comment index
        """
        cmt = ''
        if IDA_SDK_VERSION < 640:
            nextline = idaapi.ExtraGet(addr, extra)
        else:
            nextline = idaapi.get_extra_cmt(addr, extra)
        while (nextline != None):
            # workaround for tag_remove bug is to add space
            cmt += idaapi.tag_remove(nextline + ' ')
            extra += 1
            if IDA_SDK_VERSION < 640:
                nextline = idaapi.ExtraGet(addr, extra)
            else:
                nextline = idaapi.get_extra_cmt(addr, extra)
            if (nextline != None):
                cmt += '\n' 
        self.export_comment(addr, cmt_type, cmt)


    def export_functions(self):
        """
        Exports information about all functions. 
        """
        functions = idautils.Functions()
        if functions == None:
            return
        self.update_status(FUNCTIONS)
        timer = time.clock()
        self.start_element(FUNCTIONS, True)
        for addr in functions:
            function = idaapi.get_func(addr)
            if idaapi.is_spec_ea(function.startEA) == True:
                continue
            self.start_element(FUNCTION)
            self.write_address_attribute(ENTRY_POINT, function.startEA)
            if idaapi.has_user_name(idaapi.getFlags(addr)) == True:
                name = self.get_symbol_name(addr)
                if name != None and len(name) > 0:
                    self.write_attribute(NAME, name)
            if function.flags & idaapi.FUNC_LIB != 0:
                self.write_attribute(LIBRARY_FUNCTION, "y")
            self.close_tag(True)
            fchunks = idautils.Chunks(addr)
            for (startEA, endEA) in fchunks:
                self.start_element(ADDRESS_RANGE)
                self.write_address_attribute(START, startEA)
                self.write_address_attribute(END, endEA-1)
                self.close_tag()
            regcmt = idaapi.get_func_cmt(function, False)
            if regcmt != None:
                self.export_regular_cmt(regcmt)
            rptcmt = idaapi.get_func_cmt(function, True)
            if rptcmt != None:
                self.export_repeatable_cmt(rptcmt)
            demangled = idaapi.get_demangled_name(BADADDR, addr,
                                            DEMANGLED_TYPEINFO,
                                            self.inf.demnames, True)
            if demangled != None and demangled == "'string'":
                demangled = None
            outbuf = ''
            if IDA_SDK_VERSION < 660:
                outbuf = idaapi.print_type(addr, False)
            else:
                outbuf = idaapi.print_type(addr, outbuf)
            has_typeinfo = (demangled != None or (outbuf != None and
                            len(outbuf) > 0))
            if demangled != None:
                self.export_typeinfo_cmt(demangled)
            elif has_typeinfo == True:
                self.export_typeinfo_cmt(outbuf[:-1])
            self.export_stack_frame(function)
            self.end_element(FUNCTION)
        self.end_element(FUNCTIONS)
        self.display_cpu_time(timer)


    def export_manual_instruction(self, addr):
        """
        Exports user-entered "manual instruction" at an address.
        
        Args:
            addr: Integer representing instruction address.
        """
        text = idaapi.get_manual_insn(addr)
        if text == None or len(text) == 0:
            return
        self.start_element(MANUAL_INSTRUCTION)
        self.write_address_attribute(ADDRESS, addr)
        self.close_tag(True)
        self.write_text(text)
        self.end_element(MANUAL_INSTRUCTION, False)
        

    def export_manual_operand(self, addr):
        """
        Exports user-entered "manual operands" at an address.
        
        Args:
            addr: Integer representing instruction address.
        """
        for op in range(idaapi.UA_MAXOP):
            if idaapi.is_forced_operand(addr, op) == True:
                text = idaapi.get_forced_operand(addr, op)
                if text != None and len(text) > 0:
                    self.start_element(MANUAL_OPERAND)
                    self.write_address_attribute(ADDRESS, addr)
                    self.write_numeric_attribute(OPERAND_INDEX, op, 10)
                    self.close_tag(True)
                    self.write_text(text)
                    self.end_element(MANUAL_OPERAND, False)


    def export_markup(self):
        """
        Exports markup for instructions and data items including references
        and manual instructions and operands.
        """
        self.update_status(MARKUP)
        timer = time.clock()
        self.start_element(MARKUP, True)
        addr = self.min_ea
        while addr != BADADDR:
            f = idaapi.getFlags(addr)
            if self.options.MemoryReferences.checked == True:
                if idaapi.hasRef(f) == True:
                    self.export_user_memory_reference(addr)
                if idaapi.isOff(f, idaapi.OPND_ALL) == True:
                    self.export_memory_references(addr)
            if (self.options.Functions.checked == True and
               self.options.StackReferences.checked == True and
               idaapi.isStkvar(f, idaapi.OPND_ALL) == True):
               self.export_stack_reference(addr)
            if (self.options.DataTypes.checked == True and
                    idaapi.isEnum(f, idaapi.OPND_ALL) == True):
                self.export_enum_references(addr)
            if self.options.Manual.checked == True:
                if idaapi.isFop(f, idaapi.OPND_ALL)   == True:
                    self.export_manual_operand(addr)
                if idaapi.is_manual_insn(addr) == True:
                    self.export_manual_instruction(addr)
            addr = idaapi.next_head(addr, self.max_ea)
        self.end_element(MARKUP)
        self.display_cpu_time(timer)


    def export_members(self, s):
        """
        Exports the members of a structure or union.
        
        Args:
            s: IDA structure/union instance
        """
        nmembers = s.memqty
        for n in range(nmembers):
            m = s.get_member(n)
            offset = m.soff
            if s.is_union() == True:
                offset = 0
            self.start_element(MEMBER)
            self.write_numeric_attribute(OFFSET, offset)
            mname = idaapi.get_member_name(m.id)
            if len(mname) > 0:
                self.write_attribute(NAME, mname)
            dtype = self.get_member_type(m)
            if idaapi.is_varmember(m) == True:
                msize = 0
                size  = 0
            else:
                mtibuf = idaapi.opinfo_t()
                mti = idaapi.retrieve_member_info(m, mtibuf)
                if IDA_SDK_VERSION < 640:
                    msize = idaapi.get_type_size0(None, dtype)
                    if msize == None or msize == 0:
                        msize = idaapi.get_member_size(m)
                else:
                    msize = idaapi.get_data_type_size(m.flag, mtibuf)
                size = idaapi.get_member_size(m)
                osize = size
                if size < msize: size = msize
            if (size != msize):
                arraytype = self.get_member_type(m)
                dtype = "%s[%d]" % (arraytype, size/msize)
            self.write_attribute(DATATYPE, dtype)
            self.write_numeric_attribute(SIZE, size*self.cbsize)
            regcmt = idaapi.get_member_cmt(m.id, False)
            rptcmt = idaapi.get_member_cmt(m.id, True)
            hascmt = regcmt != None or rptcmt != None
            self.close_tag(hascmt)
            if (hascmt):
                if regcmt != None:
                    self.export_regular_cmt(regcmt)
                if rptcmt != None:
                    self.export_repeatable_cmt(rptcmt)
                self.end_element(MEMBER)


    def export_memory_contents(self, binfilename, binfile, start, end):
        """
        Exports the binary memory contents in the database.
        
        A MEMORY_CONTENTS element is generated for each contiguous address
        range where each address in the range contains a value.
        The binary values are store in a separate file (not the XML file),
        and the MEMORY_CONTENTS element identifies the file and the
        offset in the file where the address range is located.

        Args:
            binfilename: String containing the absolute filepath
            binfile: IDA file instance for binary file
            start: Integer representing the starting address
            end: Integer representing the ending address
        """
        length = 0
        startaddr = start
        for addr in range(start, end):
            # reset start address when length == 0
            if (length == 0):
                startaddr = addr
            has_val = idaapi.hasValue(idaapi.getFlags(addr))
            if has_val == True:
                length += self.cbsize
            next_address = idaapi.nextaddr(addr)
            if ((has_val == False) or (next_address != addr+1) or
                    (next_address == end)):
                if length > 0:
                    offset = binfile.tell()
                    idaapi.base2file(binfile.get_fp(), offset, startaddr,
                                     startaddr+length)
                    self.start_element(MEMORY_CONTENTS)
                    self.write_address_attribute(START_ADDR, startaddr)
                    self.write_attribute(FILE_NAME, binfilename)
                    self.write_numeric_attribute(FILE_OFFSET, offset)
                    self.write_numeric_attribute(LENGTH, length)
                    self.close_tag(False)
                    length=0


    def export_memory_map(self):
        """
        Exports information about all memory blocks in the database.
        
        A MEMORY_SECTION is generated for each block (segment). If the
        memory block is initialized (has values), the contents are exported
        using the MEMORY_CONTENTS element.
        """
        nsegs = idaapi.get_segm_qty()
        if (nsegs == 0):
            return
        self.update_status(MEMORY_MAP)
        timer = time.clock();
        binfilename = ''
        if (self.options.MemoryContent.checked == True):
            (binfilename, ext) = os.path.splitext(self.filename)
            binfilename += ".bytes"
            self.binfile = idaapi.qfile_t()
            self.binfile.open(binfilename,'wb');
        self.start_element(MEMORY_MAP, True)
        for i in range(nsegs):
            self.export_memory_section(idaapi.getnseg(i), binfilename)
        self.end_element(MEMORY_MAP)
        if (self.options.MemoryContent.checked == True):
            self.close_binfile()
        self.display_cpu_time(timer)


    def export_memory_reference(self, addr, op):
        """
        Exports the memory reference for operand at the address.
        
        Args:
            addr: Integer representing the instruction address.
            op: Integer representing the operand index (0-based)
        """
        f = idaapi.getFlags(addr)
        ri = idaapi.refinfo_t()
        if idaapi.get_refinfo(addr, op, ri) == 1: 
            if ri.target != BADADDR:
                target = ri.target
            elif idaapi.isCode(f) == True:
                idaapi.decode_insn(addr)
                target = idaapi.cmd.Operands[op].value - ri.tdelta + ri.base
            elif idaapi.isData(f) == True:
                target = self.get_data_value(addr) - ri.tdelta + ri.base;
            else:
                return
        else:
            return
        if idaapi.isEnabled(target) == False:
            return
        self.start_element(MEMORY_REFERENCE)
        self.write_address_attribute(ADDRESS, addr)
        self.write_numeric_attribute(OPERAND_INDEX, op, 10)
        self.write_address_attribute(TO_ADDRESS, target)
        self.write_attribute(PRIMARY, "y")
        self.close_tag()
        

    def export_memory_references(self, addr):
        """
        Exports the memory references for any operands at the address.
        
        Args:
            addr: Integer representing the instruction address.
        """
        f = idaapi.getFlags(addr)
        for op in range(idaapi.UA_MAXOP):
            if idaapi.isOff(f, op) == True and (idaapi.isData(f) == True or
                    (idaapi.isCode(f) == True and
                    self.is_imm_op(addr, op) == True)):
                self.export_memory_reference(addr, op)
    

    def export_memory_section(self, seg, binfilename):
        """
        Exports segment information as a MEMORY_SECTIONS element.
        
        Args:
            seg: IDA segment instance
            binfilename: String containing absolute filepath for binary file.
        """
        segname = idaapi.get_segm_name(seg)
        self.start_element(MEMORY_SECTION)
        self.write_attribute(NAME, segname)
        self.write_address_attribute(START_ADDR, seg.startEA)
        length = (seg.endEA - seg.startEA)*self.cbsize
        self.write_numeric_attribute(LENGTH, length)
        perms = ""
        if (seg.perm != 0):
            if (seg.perm & idaapi.SEGPERM_READ  != 0):
                perms += 'r'
            if (seg.perm & idaapi.SEGPERM_WRITE != 0):
                perms += 'w' 
            if (seg.perm & idaapi.SEGPERM_EXEC  != 0):
                perms += 'x'
            if (len(perms) > 0):
                self.write_attribute(PERMISSIONS, perms)
        has_contents = (self.options.MemoryContent.checked == True and
                       self.check_if_seg_contents(seg) == True)
        self.close_tag(has_contents)
        if (has_contents == True):
            self.export_memory_contents(os.path.basename(binfilename),
                                      self.binfile, seg.startEA, seg.endEA)
            self.end_element(MEMORY_SECTION)


    def export_program(self):
        """
        Exports basic information about the program as the PROGRAM,
        INFO_SOURCE, PROCESSOR, and COMPILER elements.
        """
        # output the PROGRAM element
        self.update_status(PROGRAM);
        timer = time.clock()
        self.start_element(PROGRAM)
        self.write_attribute(NAME, idaapi.get_root_filename())
        self.write_attribute(EXE_PATH, idaapi.get_input_file_path())
        etype = idaapi.get_file_type_name()
        if (len(etype) > 0):
            self.write_attribute(EXE_FORMAT, etype)
        # check for presence of INPUT_MD5 netnode
        md5 = idaapi.netnode(INPUT_MD5)
        if md5 == idaapi.BADNODE:
            input_md5 = idc.GetInputMD5()
        else:
            input_md5 = md5.supval(idaapi.RIDX_MD5)
        if input_md5 != None:
            self.write_attribute(INPUT_MD5,input_md5)
        self.close_tag(True)
    
        # output the INFO_SOURCE element
        self.start_element(INFO_SOURCE)
        if IDA_SDK_VERSION < 670:
            tool  = 'IDA-Pro XML plugin (Python) SDK '
            tool +=  str(IDA_SDK_VERSION)
        else:
            tool  = 'IDA-Pro ' + idaapi.get_kernel_version()
            tool += ' XML plugin (Python) SDK ' + str(IDA_SDK_VERSION)
        self.write_attribute(TOOL, tool)
        user = os.getenv("USERNAME", "UNKNOWN")
        if (user == "UNKNOWN"):
            user = os.getenv("USER", "UNKNOWN")
        self.write_attribute(USER, user)
        self.write_attribute(FILE, idaapi.cvar.database_idb)
        ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        self.write_attribute(TIMESTAMP, ts)
        self.close_tag()
    
        # output the PROCESSOR element
        self.start_element(PROCESSOR)
        self.write_attribute(NAME, self.inf.procName)
        if (self.inf.mf):
            byte_order ="big"
        else:
            byte_order ="little"
        self.write_attribute(ENDIAN, byte_order)
        self.seg_addr = False
        bitness = 1
        model_warning = False
        nsegs = idaapi.get_segm_qty()
        if (nsegs > 0):
            bitness = idaapi.getnseg(0).bitness
            for i in range(1,nsegs):
                seg = idaapi.getnseg(i)
                if (seg.bitness != bitness):
                    model_warning = True
                if (seg.bitness > bitness):
                    bitness = seg.bitness
        addr_model = "32-bit"
        if (bitness == 0):
            addr_model = "16-bit"
        elif (bitness == 2):
            addr_model = "64-bit"
        self.write_attribute(ADDRESS_MODEL, addr_model)
        self.close_tag()
        if (model_warning):
            idaapi.msg("WARNING: Segments do not have same " +
                       "addressing model!\n")
        if (idaapi.ph.id == idaapi.PLFM_386 and bitness == 0):
            self.seg_addr = True
        # find any overlayed memory before processing addressable items
        self.find_overlay_memory()
    
        # output compiler info
        self.start_element(COMPILER)
        self.write_attribute(NAME, idaapi.get_compiler_name(self.inf.cc.id))
        self.close_tag()
        self.display_cpu_time(timer)


    def export_program_entry_points(self):
        """
        Exports entry points for the program.
        """
        nepts = idaapi.get_entry_qty()
        if (nepts  == 0):
            return
        self.update_status(PROGRAM_ENTRY_POINTS)
        timer = time.clock()
        self.start_element(PROGRAM_ENTRY_POINTS, True)
        for i in range(nepts):
            self.start_element(PROGRAM_ENTRY_POINT)
            addr = idaapi.get_entry(idaapi.get_entry_ordinal(i))
            self.write_address_attribute(ADDRESS, addr)
            self.close_tag()
        self.end_element(PROGRAM_ENTRY_POINTS)
        self.display_cpu_time(timer)


    def export_register_values(self):
        """
        Exports segment register value ranges.
        """
        # should use legacy function if IDA > 660
        if IDA_SDK_VERSION < 670:
            self.export_register_values_legacy()
            return
        first = idaapi.ph_get_regFirstSreg()
        last  = idaapi.ph_get_regLastSreg()+1
        has_segregareas = False
        for j in range(first, last):
            nsegregareas = idaapi.get_srareas_qty2(j)
            if nsegregareas != 0:
                has_segregareas = True
                break;
        if has_segregareas == False:
            return
        self.update_status(REGISTER_VALUES)
        timer = time.clock();
        self.start_element(REGISTER_VALUES, True)
        sr = idaapi.segreg_area_t()
        for j in range(first, last):
            nsegregareas = idaapi.get_srareas_qty2(j)
            if nsegregareas == 0:
                continue
            for i in range(nsegregareas):
                success = idaapi.getn_srarea2(sr, j, i)
                if success == False:
                    continue
                value = sr.val
                if value == idaapi.BADSEL:
                    continue
                regname = idaapi.ph.regnames[j]
                if regname == None:
                    continue
                if regname.lower() == "cs":
                    continue
                if (idaapi.ph.id == idaapi.PLFM_TMS and
                    regname.lower() == "ds"):
                    continue
                self.start_element(REGISTER_VALUE_RANGE)
                self.write_attribute(REGISTER, idaapi.ph.regnames[j])
                self.write_numeric_attribute(VALUE, value)
                self.write_address_attribute(START_ADDRESS, sr.startEA)
                length = (sr.endEA - sr.startEA) * self.cbsize
                self.write_numeric_attribute(LENGTH, length)
                self.close_tag()
        self.end_element(REGISTER_VALUES)
        self.display_cpu_time(timer)


    def export_register_values_legacy(self):
        """
        Exports segment register value ranges.
        """
        # should use export_register_values() if IDA > 660
        if IDA_SDK_VERSION > 660:
            return
        if IDA_SDK_VERSION < 650:
            n = 0
            srarea = idaapi.getnSRarea(n)
            while type(srarea) == idaapi.segreg_t:
                n += 1
                srarea = idaapi.getnSRarea(n)
            nsegregareas = n
        else:
            nsegregareas = idaapi.get_srareas_qty()
        if (nsegregareas == 0):
            return
        self.update_status(REGISTER_VALUES)
        timer = time.clock();
        self.start_element(REGISTER_VALUES, True)
        for i in range(nsegregareas):
            if IDA_SDK_VERSION < 650:
                sr = idaapi.getnSRarea(i)
            else:
                sr = idaapi.getn_srarea(i)
            first = idaapi.ph_get_regFirstSreg()
            last  = idaapi.ph_get_regLastSreg()+1
            for j in range(first, last):
                value = idaapi.getSR(sr.startEA, j)
                if value == idaapi.BADSEL:
                    continue
                regname = idaapi.ph.regnames[j]
                if regname == None:
                    continue
                if regname.lower() == "cs":
                    continue
                if (idaapi.ph.id == idaapi.PLFM_TMS and
                    regname.lower() == "ds"):
                    continue
                self.start_element(REGISTER_VALUE_RANGE)
                self.write_attribute(REGISTER, idaapi.ph.regnames[j])
                self.write_numeric_attribute(VALUE, value)
                self.write_address_attribute(START_ADDRESS, sr.startEA)
                length = (sr.endEA - sr.startEA) * self.cbsize
                self.write_numeric_attribute(LENGTH, length)
                self.close_tag()
        self.end_element(REGISTER_VALUES)
        self.display_cpu_time(timer)


    def export_regular_cmt(self, cmt):
        """
        Exports the regular comment for an item.
        
        Args:
            cmt: String containing the regular comment.
        """
        self.write_comment_element(REGULAR_CMT, cmt)
    

    def export_repeatable_cmt(self, cmt):
        """
        Exports the repeatable comment for an item.
        
        Args:
            cmt: String containing the repeatable comment.
        """
        self.write_comment_element(REPEATABLE_CMT, cmt)
    

    def export_stack_frame(self, function):
        """
        Export information about a function stack frame including
        variables allocated on the stack.
        
        Args:
            function: IDA function instance
        """
        sframe = idaapi.get_struc(function.frame)
        if sframe == None or sframe.memqty <= 0:
            return
        self.start_element(STACK_FRAME)
        self.write_numeric_attribute(LOCAL_VAR_SIZE, function.frsize)
        self.write_numeric_attribute(REGISTER_SAVE_SIZE, function.frregs)
        retsize = idaapi.get_frame_retsize(function)
        self.write_numeric_attribute(RETURN_ADDR_SIZE, retsize)
        self.write_numeric_attribute(BYTES_PURGED, function.argsize)
        has_stack_vars = self.check_stack_frame(sframe)
        self.close_tag(has_stack_vars)
        if has_stack_vars == True:
            self.export_stack_vars(function, sframe)
            self.end_element(STACK_FRAME)

    def export_stack_reference(self, addr):
        """
        Exports references to stack variables at the address.
        
        Args:
            addr: Integer containing instruction address.
        """
        f = idaapi.getFlags(addr)
        for op in range(idaapi.UA_MAXOP):
            if idaapi.isCode(f) == True and idaapi.isStkvar(f, op) == True:
                idaapi.decode_insn(addr)
                opnd = idaapi.cmd.Operands[op]
                optype = idaapi.op_t_get_type(opnd)
                if optype == idaapi.o_void:
                    continue
                SV = idaapi.get_stkvar(opnd, idaapi.op_t_get_addr(opnd))
                if SV == None:
                    continue
                (sv, actval) = SV
                function = idaapi.get_func(addr)
                self.start_element(STACK_REFERENCE)
                self.write_address_attribute(ADDRESS, addr)
                self.write_numeric_attribute(OPERAND_INDEX, op, 10)
                offset = opnd.addr
                spoff = offset - function.frregs
                if offset > 0x7FFFFFFF:
                    offset -= 0x100000000
                if spoff > 0x7FFFFFFF:
                    spoff  -= 0x100000000
                self.write_numeric_attribute(STACK_PTR_OFFSET,
                                             spoff,
                                             16, True)
                if (function.flags & FUNC_FRAME) != 0:
                    self.write_numeric_attribute(FRAME_PTR_OFFSET,
                                                offset,
                                                16, True)
                self.close_tag()

    def export_stack_vars(self, function, sframe):
        """
        Exports the stack variables (parameters and locals) in a stack frame.
        
        Args:
            function: IDA function instance.
            sframe: IDA stack frame instance.
        """
        for i in range(sframe.memqty):
            member = sframe.get_member(i)
            if member == None:
                continue
            mname = idaapi.get_member_name(member.id)
            if mname == None or len(mname) < 0:
                continue
            if mname == " s" or mname == " r":
                continue
            spoff = member.soff - function.frsize - function.frregs
            froff = member.soff - function.frsize
            self.start_element(STACK_VAR)
            self.write_numeric_attribute(STACK_PTR_OFFSET, spoff, 16, True)
            if function.flags & idaapi.FUNC_FRAME != 0:
                self.write_numeric_attribute(FRAME_PTR_OFFSET, froff, 16, True)
            pre = mname[0:4]
            if pre != "var_" and pre != "arg_":
                self.write_attribute(NAME, mname)
            f = member.flag
            size = idaapi.get_member_size(member)
            mtype = self.get_member_type(member)
            msize = size
            if idaapi.isStruct(f) == True:
                msize = idaapi.get_struc_size(idaapi.get_struc_id(mtype))
            elif idaapi.isASCII(f) == False:
                mtibuf = idaapi.opinfo_t()
                mti = idaapi.retrieve_member_info(member, mtibuf)
                if IDA_SDK_VERSION < 640:
                    msize = idaapi.get_type_size0(None, mtype)
                    if msize == None or msize == 0:
                        msize = idaapi.get_member_size(member)
                else:
                    msize = idaapi.get_data_type_size(f, mtibuf)
            if size < msize: size = msize
            if (idaapi.isASCII(f) == False and idaapi.isAlign(f) == False
                and size != msize):
                mtype = "%s[%d]" % (mtype, size/msize)
            self.write_attribute(DATATYPE, mtype)
            self.write_numeric_attribute(SIZE, size*self.cbsize)
            regcmt = idaapi.get_member_cmt(member.id, False)
            rptcmt = idaapi.get_member_cmt(member.id, True)
            if regcmt != None:
                regcmt  = idaapi.tag_remove(regcmt + " ", 0)
            if rptcmt != None:
                rptrcmt = idaapi.tag_remove(rptcmt + " ", 0)
            has_regcmt = regcmt != None and len(regcmt) > 0
            has_rptcmt = rptcmt != None and len(rptcmt) > 0
            has_content = has_regcmt or has_rptcmt
            self.close_tag(has_content)
            if has_content == True:
                if has_regcmt == True:
                    self.export_regular_cmt(regcmt)
                if has_rptcmt == True:
                    self.export_repeatable_cmt(rptcmt)
                self.end_element(STACK_VAR)


    def export_structures(self):
        """
        Exports information about all structures and unions.
        """
        structs = idautils.Structs()
        for struct in structs:
            (sid, idx, sname) = struct
            s = idaapi.get_struc(idx)
            stype = STRUCTURE
            if s.is_union() == True:
                stype = UNION
            self.start_element(stype)
            self.write_attribute(NAME, sname)
            size = idaapi.get_struc_size(idx)*self.cbsize
            self.write_numeric_attribute(SIZE, size)
            if s.is_varstr() == True:
                self.write_attribute(VARIABLE_LENGTH, "y")
            regcmt = idaapi.get_struc_cmt(idx, False)
            rptcmt = idaapi.get_struc_cmt(idx, True)
            has_contents = regcmt != None or rptcmt != None or s.memqty > 0
            self.close_tag(has_contents)
            if (has_contents):
                if regcmt != None:
                    self.export_regular_cmt(regcmt)
                if rptcmt != None:
                    self.export_repeatable_cmt(rptcmt)
                if s.memqty > 0:
                    self.export_members(s)
                self.end_element(stype)
    

    def export_symbol(self, addr, name, stype=""):
        """
        Exports name for an address as a SYMBOL element. If the name is a
        demangled name, add the mangled name as the MANGLED attribute.
        
        Args:
            addr: Integer representing the symbol address.
            name: String containing the symbol name.
            stype: String indicating symbol type (global or local)
        """
        self.start_element(SYMBOL)
        self.write_address_attribute(ADDRESS, addr)
        self.write_attribute(NAME, name)
        self.write_attribute(TYPE, stype)
        mangled = idaapi.get_name(BADADDR, addr)
        if name != None and mangled != name:
            self.write_attribute("MANGLED", mangled)
        self.close_tag()
        

    def export_symbol_table(self):
        """
        Exports user-defined and non-default names as SYMBOL elements.
        """
        addr = self.min_ea
        if idaapi.has_any_name(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.has_any_name)
        if addr == BADADDR:
            return
        self.update_status(SYMBOL_TABLE)
        self.start_element(SYMBOL_TABLE, True)
        timer = time.clock()
        while addr != BADADDR:
            # only export meaningful names (user and auto)
            f = idaapi.getFlags(addr)
            if (idaapi.has_user_name(f) == True or
                idaapi.has_auto_name(f) == True):
                # check for global name
                name = self.get_symbol_name(addr)
                if name != None and len(name) > 0:
                    self.export_symbol(addr, name)
                # check for local name
                if ((IDA_SDK_VERSION > 620 and
                    idaapi.has_lname(addr)) or
                    idaapi.get_aflags(addr) & idaapi.AFL_LNAME):
                    if IDA_SDK_VERSION < 680:
                        name = idaapi.get_name(addr, addr)
                    else:
                        name = idaapi.get_ea_name(addr, idaapi.GN_LOCAL)
                    if name != None and len(name) > 0:
                        self.export_symbol(addr, name, 'local')
            # get next address with any name
            addr = idaapi.nextthat(addr, self.max_ea,
                                   idaapi.has_any_name)
        self.end_element(SYMBOL_TABLE)
        self.display_cpu_time(timer)
        

    def export_typeinfo_cmt(self, cmt):
        """
        Exports comment containing type information for data and functions.
        
        Args:
            cmt: String containing type info.
        """
        # older versions of IDAPython returned a '\n' at end of cmt
        if cmt[-1] == '\n':
            cmt = cmt[0:-1]
        self.write_comment_element(TYPEINFO_CMT, cmt)
        

    def export_user_memory_reference(self, addr):
        """
        Exports a user-specified memory reference at the address.
        
        Args:
            addr: Integer representing the instruction address.
        """
        for xref in idautils.XrefsTo(addr, idaapi.XREF_FAR):
            if xref.user == 1:
                self.start_element(MEMORY_REFERENCE)
                self.write_address_attribute(ADDRESS, xref.frm)
                self.write_address_attribute(TO_ADDRESS, xref.to)
                self.write_attribute(USER_DEFINED, "y")
                self.close_tag()


    def find_overlay_memory(self):
        """
        Determines if any memory blocks (segments) are overlays.
        
        A segment is an overlay if it translates to the same logical
        address as another segment. This is rare, but may occur, for
        example when a processor has a small logical address space
        (i.e. a 16-bit address is limited to 64K) and multiple physical
        segments are mapped into the same logical segment.
        """
        self.overlay = dict()
        self.has_overlays = False;
        nsegs = idaapi.get_segm_qty()
        if nsegs == 0:
            return
        s = idaapi.getnseg(0)
        start = self.translate_address(s.startEA)
        self.overlay[start] = False
        for i in range(1, nsegs):
            s = idaapi.getnseg(i)
            space = self.get_space_name(s.startEA)
            saddr = self.translate_address(s.startEA)
            eaddr = self.translate_address(s.endEA-1)
            is_overlay = False
            for j in range(i):
                s2 = idaapi.getnseg(j)
                space2 = self.get_space_name(s2.startEA)
                if space == space2:
                    start = self.translate_address(s2.startEA)
                    end   = self.translate_address(s2.endEA-1)
                    if ((saddr >= start and saddr <= end) or 
                        (eaddr >= start and eaddr <= end)):
                        is_overlay = True
                        self.has_overlays = True
                        break
            self.overlay[saddr] = is_overlay


    def get_address_string(self, addr):
        """
        Returns a string representing the address.
        
        The representation is typically a hex string of the address,
        but may include a segment or space name prefixe based on the
        processor or architecture.
        
        Args:
            addr: Integer representing a program address.
        """
        temp = "0x%X" % (addr - idaapi.get_segm_base(idaapi.getseg(addr)))
        space = self.get_space_name(addr)
        if space != None:
            temp = "%s:%04X" % (space,
                            addr - idaapi.get_segm_base(idaapi.getseg(addr)))
        else:
            if (idaapi.ph_get_id() == idaapi.PLFM_386 and
                idaapi.getseg(addr).bitness == 0):
                base = idaapi.get_segm_para(idaapi.getseg(addr))
                temp = "%04X:%04X" % (base, addr - (base << 4))
        if idaapi.ph_get_id() == idaapi.PLFM_C166:
            temp = "0x%X" % addr
        if self.has_overlays == True and self.is_overlay(addr) == True:
            oname = idaapi.get_segm_name(idaapi.getseg(addr))
            if len(oname) > 0:
                temp = oname + "::" + temp
        return temp


    def get_data_value(self, addr):
        """
        Returns the data item value at an address based on its size.
        
        Args:
            addr: Integer representing a program address.
        """
        size = idaapi.get_item_size(addr)*self.cbsize
        if size == 1:   return idaapi.get_byte(addr)
        if size == 2:   return idaapi.get_16bit(addr)
        if size == 4:   return idaapi.get_32bit(addr)
        if size == 8:   return idaapi.get_64bit(addr)
        return 0
    

    def get_datatype(self, addr):
        """
        Returns the datatype at an address.
        
        The type could be a basic type (byte, word, dword, etc.),
        a structure, an array, a pointer, or a string type.
        
        Args:
            addr: Integer representing a program address.
        """
        f = idaapi.getFlags(addr)
        t = self.get_type(f)
        if idaapi.isStruct(f) == True:
            opndbuf = idaapi.opinfo_t()
            opnd = idaapi.get_opinfo(addr, 0, f, opndbuf)
            return idaapi.get_struc_name(opnd.tid)
        if idaapi.isASCII(f) == True:
            str_type = idc.GetStringType(addr)
            if str_type == idaapi.ASCSTR_TERMCHR:   return "string"
            if str_type == idaapi.ASCSTR_PASCAL:    return "string1"
            if str_type == idaapi.ASCSTR_LEN2:      return "string2"
            if str_type == idaapi.ASCSTR_LEN4:      return "string4"
            if str_type == idaapi.ASCSTR_UNICODE:   return "unicode"
            if str_type == idaapi.ASCSTR_ULEN2:     return "unicode2"
            if str_type == idaapi.ASCSTR_ULEN4:     return "unicode4"
            return "string"
        if idaapi.isOff0(f) == True: return "pointer"
        return t


    def get_format(self, flags):
        """
        Returns the display format of a data item based on its flags.
        
        Args:
            flags: Integer representing IDA item flags
            
        Returns:
            String representing IDA display format.
        """
        if idaapi.isChar0(flags): return "char"
        radix = idaapi.getRadix(flags, 0)
        if radix == 2:  return "binary"
        if radix == 8:  return "octal"
        if radix == 10: return "decimal"
        return "hex" # default
    

    def get_member_type(self, m):
        """
        Returns the datatype of a structure member.
        
        Args:
            m: IDA member instance.
            
        Returns:
            String representing member datatype.
        """
        f = m.flag
        t = self.get_type(f)
        if idaapi.isOff0(f) == True:
            t = "pointer"
        if idaapi.isStruct(f) == False:
            return t
        s = idaapi.get_sptr(m)
        if (s == None):
            return t
        sname = idaapi.get_struc_name(s.id)
        if (sname == None):
            return t
        return sname


    def get_options(self):
        """
        Displays the options menu and retrieves the option settings. 
        """
        fmt =  "HELP\n"
        fmt += "XML plugin (Python)\n"
        fmt += "IDA SDK: "+ str(IDA_SDK_VERSION) + "\n"
        fmt += "\n"
        fmt += "The XML interface provides a dump of the IDA-Pro database as "
        fmt += "a XML \"PROGRAM\" document. The XML PROGRAM document contains "
        fmt += "information from the idb file in a readable text format, and "
        fmt += "can be viewed with a text editor or web browser.\n\n"
        fmt += "ENDHELP\n"
        fmt += "Export as XML PROGRAM document...."
        fmt += "\n <##Options##Memory Sections:{MemorySections}>"
        fmt += "\n <Memory Content:{MemoryContent}>"
        fmt += "\n <Segment Register Value Ranges:{RegisterValues}>"
        fmt += "\n <Data Types:{DataTypes}>"
        fmt += "\n <Code Blocks:{CodeBlocks}>"
        fmt += "\n <Data Definitions:{DataDefinitions}>"
        fmt += "\n <Comments:{Comments}>"
        fmt += "\n <Entry Points:{EntryPoints}>"
        fmt += "\n <Symbols:{Symbols}>"
        fmt += "\n <Functions:{Functions}>"
        fmt += "\n <Memory References:{MemoryReferences}>"
        fmt += "\n <Stack References:{StackReferences}>"
        fmt += "\n <Manual Instructions/Operands:{Manual}>{cGroup1}>"
        fmt += "\n\n"
        
        Opts = { 'cGroup1': idaapi.Form.ChkGroupControl
            (
                (
                "MemorySections",
                "MemoryContent",
                "RegisterValues",
                "DataTypes",
                "CodeBlocks",
                "DataDefinitions",
                "Comments",
                "EntryPoints",
                "Symbols",
                "Functions",
                "MemoryReferences",
                "StackReferences",
                "Manual"
                )
            )
        }
        
        self.options = idaapi.Form(fmt, Opts)
        self.options.Compile()
        
        self.options.MemorySections.checked   = True
        self.options.MemoryContent.checked    = True
        self.options.DataTypes.checked        = True
        self.options.RegisterValues.checked   = True
        self.options.CodeBlocks.checked       = True
        self.options.DataDefinitions.checked  = True
        self.options.Symbols.checked          = True
        self.options.EntryPoints.checked      = True
        self.options.Functions.checked        = True
        self.options.Comments.checked         = True
        self.options.MemoryReferences.checked = True
        self.options.StackReferences.checked  = False
        self.options.Manual.checked           = True
    
        if (self.autorun == False):
            ok = self.options.Execute()
            if (ok == 0):
                raise Cancelled


    def get_space_name(self, addr):
        """
        Returns the memory space name associated with an address.
        
        Args:
            addr: Integer representing a program address.
            
        Returns:
            String containg the memory space name.
            None if single address space architecture.

        Used for Harvard architectures (Intel 8051 and TMS, add others
        as needed). 
        """
        pid = idaapi.ph_get_id()
        stype = idaapi.segtype(addr)
        if pid == idaapi.PLFM_8051:
            if stype == idaapi.SEG_CODE:
                return "CODE"
            else:
                if stype == idaapi.SEG_IMEM:
                    iaddr = addr - idaapi.get_segm_base(idaapi.getseg(addr))
                    if iaddr < 0x80:
                        return "INTMEM"
                    else:
                        return "SFR"
                else:
                    return "EXTMEM"
        if pid == idaapi.PLFM_TMS:
            if stype == idaapi.SEG_CODE:
                return "CODE"
            else:
                return "DATA"
        return None


    def get_symbol_name(self, ea):
        """
        Returns the symbol name for the address.
        
        Args:
            ea: Integer representing the symbol address.
            
        Returns:
            String containing the symbol name.
        
        The demangled name will be returned if it exists, otherwise the
        displayed name is returned. Spaces (' ') will be replaced with '_'.
        """
        flags = idaapi.getFlags(ea)
        name = idaapi.get_demangled_name(BADADDR, ea, DEMANGLED_FORM,
                                         self.inf.demnames, False)
        if name == None or len(name) == 0 or name == "`string'":
            name = idaapi.get_name(BADADDR, ea)
        if name != None:
            name = name.replace(" ","_")
        return name


    def get_type(self, flags):
        """
        Returns a datatype string based on the item flags.
        
        Args:
            flags: IDA item flags.
            
        Returns:
            String representing item datatype.
        """
        if (self.cbsize == 2):
            if idaapi.isByte(flags) == True: return "word"
            if idaapi.isWord(flags) == True: return "dword"
        if idaapi.isByte(flags)     == True: return "byte"
        if idaapi.isWord(flags)     == True: return "word"
        if idaapi.isDwrd(flags)     == True: return "dword"
        if idaapi.isQwrd(flags)     == True: return "qword"
        if idaapi.isOwrd(flags)     == True: return "oword"
        if idaapi.isTbyt(flags)     == True: return "tbyte"
        if idaapi.isFloat(flags)    == True: return "float"
        if idaapi.isDouble(flags)   == True: return "double"
        if idaapi.isPackReal(flags) == True: return "packed"
        if idaapi.isASCII(flags)    == True: return "ascii"
        if idaapi.isStruct(flags)   == True: return "structure"
        if idaapi.isAlign(flags)    == True: return "align"
        return "unknown"


    def is_imm_op(self, addr, op):
        """
        Returns true if instruction operand at address is an immediate value.
        
        Args:
            addr: Integer representing instruction address.
            op: Integer representing operand index (0-based).
            
        Returns:
            True if instruction operand at address is an immediate value.
            False otherwise.
        """
        idaapi.decode_insn(addr)
        if (idaapi.cmd.Operands[op].type == idaapi.o_imm):
            return True
        return False
        

    def is_overlay(self, addr):
        """
        Checks if memory block (segment) is an overlay.
        
        Args:
            addr: Integer representing a program address.
            
        Returns:
            True if memory block (segment) is an overlay.
        """
        if idaapi.ph_get_id() == idaapi.PLFM_C166:
            return False
        s = idaapi.getseg(addr)
        if s.startEA in self.overlay:
            return self.overlay[s.startEA]
        return False
    
    def is_signed_data(self, flags):
        return (flags & idaapi.FF_SIGN) != 0


    def open_file(self, filename, mode):
        """
        Opens filename to specified mode.
        
        Args:
            filename: String representing absolute filepath.
            mode: String representing mode for open.
            
        Returns
            File handle.
        
        Exceptions:
            Displays a warning and raises FileError exception
            if open fails.
        """
        try:
            f = open(filename, mode)
            return f
        except:
            fmt = "TITLE ERROR!\n"
            fmt += "ICON ERROR\n"
            fmt += "AUTOHIDE NONE\n"
            fmt += "HIDECANCEL\n"
            fmt += "Error opening file" + filename + "!\n"
            idaapi.warning(fmt)
            raise FileError


    def start_element(self, tag, close=False):
        """
        Outputs the start of a new element on a new indented line.
        
        Args:
            tag: String representing the element tag
            close: Boolean indicating if tag is should be closed.
        """
        if idaapi.wasBreak() == True:
            raise Cancelled
        self.write_to_xmlfile("\n" + ("    " * self.indent_level) + "<" + tag)
        if (close):
            self.close_tag(True)
        # Add a counter for this tag; increment counter if it already exists.
        if (tag in self.elements) == False:
            self.elements[tag] = len(self.elements)
            self.counters.append(1)
            self.tags.append(tag)
        else:
            self.counters[self.elements[tag]] += 1
        

    def translate_address(self, addr):
        """
        Returns the translated logical address.

        The logical address is adjusted for the segment base address.
        For 16-bit segmented memory, return the 20-bit address.
        
        Args:
            addr: Integer representing a program address.
            
        Returns:
            Integer representing the logical address.
        """
        if self.seg_addr == False:
            return addr - idaapi.get_segm_base(idaapi.getseg(addr))
        base = idaapi.get_segm_para(idaapi.getseg(addr))
        return (base << 16) + (addr - (base << 4))
    

    def update_status(self, tag):
        """
        Displays the processing status in the IDA window.
        
        Args:
            tag: String representing XML element tag
        """
        status = "Exporting <" + tag + ">"
        idaapi.msg('\n%-35s' % status)
        idaapi.hide_wait_box()
        idaapi.show_wait_box(status)


    def write_address_attribute(self, name, addr):
        """
        Outputs an address attribute for an element.
        
        Args:
            name: String representing attribute name.
            addr: Integer representing a program address.
        """
        self.write_attribute(name, self.get_address_string(addr))
    

    def write_attribute(self, name, value):
        """
        Outputs an attribute (name and value) for an element.
        
        Args:
            name: String representing attribute name.
            value: String representing attribute value.
        """
        if name == None or value == None:
            return
        if (len(name) == 0) or (len(value) == 0):
            return
        attr = " " + name + '="' + self.check_for_entities(value) + '"'
        self.write_to_xmlfile(attr)
        

    def write_comment_element(self, name, cmt):
        """
        Outputs the tag and text for a comment element.
        Comment elements can be REGULAR_CMT, REPEATABLE_CMT, or TYPEINFO_CMT.
        
        Args:
            name: String representing the comment element name.
            cmt: String containing the comment.
        """
        self.start_element(name, True)
        self.write_text(cmt)
        self.end_element(name, False)
        

    def write_numeric_attribute(self, name, value, base=16, signedhex=False):
        """
        Outputs a numeric value attribute (name and value) for an element.
        
        Args:
            name: String representing the attribute name.
            value: Integer representing the attribute value.
            base: Integer representing numeric base to use for value.
            signedhex: Boolean indicating if hex representation of
                value is signed.
        """
        if base == 10:
            temp = "%d" % value
        else:
            if signedhex == True and value < 0:
                temp = "-0x%X" % abs(value)
            else:
                temp = "0x%X" % value
        self.write_attribute(name, temp)


    def write_text(self, text):
        """
        Outputs the parsed character text for an element.
        The text is checked for special characters.
        
        Args:
            text: String representing the element text.
        """
        self.write_to_xmlfile(self.check_for_entities(text))
    

    def write_to_xmlfile(self, buf):
        """
        Writes the buffer to the XML file.
        
        Args:
            buf: String containg data to write to XML file.
        """
        self.xmlfile.write(buf)
        self.dbg(buf)
    

    def write_xml_declaration(self):
        """
        Writes the XML Declarations at the start of the XML file.
        """
        self.dbg("\n")
        xml_declaration  = "<?xml version=\"1.0\" standalone=\"yes\"?>"
        xml_declaration += "\n<?program_dtd version=\"1\"?>\n"
        self.write_to_xmlfile(xml_declaration)

# Global constants
# mangled name inhibit flags are not exposed in idaapi
# inhibit flags for symbol names
# DEMANGLE_FORM (MNG_SHORT_FORM | MNG_NOBASEDT | MNG_NOCALLC | MNG_NOCSVOL)
DEMANGLED_FORM = 0x0ea3ffe7
# inhibit flags for typeinfo cmts
# DEMANGLED_TYPEINFO (MNG_LONG_FORM)
DEMANGLED_TYPEINFO = 0x06400007

# Global XML string constants for elements and attributes
ADDRESS = 'ADDRESS'
ADDRESS_MODEL = 'ADDRESS_MODEL'
ADDRESS_RANGE = 'ADDRESS_RANGE'
BASE_ADDRESS = 'BASE_ADDRESS'
BIT_FIELD = 'BIT_FIELD'
BIT_MAPPED = 'BIT_MAPPED'
BIT_MASK = 'BIT_MASK'
BOOKMARK = 'BOOKMARK'
BOOKMARKS = 'BOOKMARKS'
BYTES = 'BYTES'
BYTES_PURGED = 'BYTES_PURGED'
CATEGORY = 'CATEGORY'
CODE = 'CODE'
CODE_BLOCK = 'CODE_BLOCK'
COMMENT = 'COMMENT'
COMMENTS = 'COMMENTS'
COMPILER = 'COMPILER'
DATA = 'DATA'
DATATYPE = 'DATATYPE'
DATATYPES = 'DATATYPES'
DATATYPE_NAMESPACE = 'DATATYPE_NAMESPACE'
DEFINED_DATA = 'DEFINED_DATA'
DESCRIPTION = 'DESCRIPTION'
DISPLAY_SETTINGS = 'DISPLAY_SETTINGS'
END = 'END'
ENDIAN = 'ENDIAN'
ENTRY_POINT = 'ENTRY_POINT'
ENUM = 'ENUM'
ENUM_ENTRY = 'ENUM_ENTRY'
EQUATE = 'EQUATE'
EQUATES = 'EQUATES'
EQUATE_GROUP = 'EQUATE_GROUP'
EQUATE_REFERENCE = 'EQUATE_REFERENCE'
EXE_FORMAT = 'EXE_FORMAT'
EXE_PATH = 'EXE_PATH'
EXT_LIBRARY = 'EXT_LIBRARY'
EXT_LIBRARY_REFERENCE = 'EXT_LIBRARY_REFERENCE'
EXT_LIBRARY_TABLE = 'EXT_LIBRARY_TABLE'
FAMILY = 'FAMILY'
FILE = 'FILE'
FILE_NAME = 'FILE_NAME'
FILE_OFFSET = 'FILE_OFFSET'
FOLDER = 'FOLDER'
FORMAT = 'FORMAT'
FRAGMENT = 'FRAGMENT'
FRAME_PTR_OFFSET = 'FRAME_PTR_OFFSET'
FUNCTION = 'FUNCTION'
FUNCTIONS = 'FUNCTIONS'
FUNCTION_DEF = 'FUNCTION_DEF'
IMAGE_BASE = 'IMAGE_BASE'
INPUT_MD5 = 'INPUT_MD5'
INFO_SOURCE = 'INFO_SOURCE'
LANGUAGE_PROVIDER = 'LANGUAGE_PROVIDER'
LENGTH = 'LENGTH'
LIB_ADDR = 'LIB_ADDR'
LIB_LABEL = 'LIB_LABEL'
LIB_ORDINAL = 'LIB_ORDINAL'
LIB_PROG_NAME = 'LIB_PROG_NAME'
LIBRARY_FUNCTION = 'LIBRARY_FUNCTION'
LOCAL_VAR_SIZE = 'LOCAL_VAR_SIZE'
MANGLED = 'MANGLED'
MANUAL_INSTRUCTION = 'MANUAL_INSTRUCTION'
MANUAL_OPERAND = 'MANUAL_OPERAND'
MARKUP = 'MARKUP'
MEMBER = 'MEMBER'
MEMORY_CONTENTS = 'MEMORY_CONTENTS'
MEMORY_MAP = 'MEMORY_MAP'
MEMORY_REFERENCE = 'MEMORY_REFERENCE'
MEMORY_SECTION = 'MEMORY_SECTION'
NAME = 'NAME'
NAMESPACE = 'NAMESPACE'
OFFSET = 'OFFSET'
OPERAND_INDEX = 'OPERAND_INDEX'
PARAM_OFFSET = 'PARAM_OFFSET'
PATH = 'PATH'
PERMISSIONS = 'PERMISSIONS'
PRIMARY = 'PRIMARY'
PROCESSOR = 'PROCESSOR'
PROGRAM = 'PROGRAM'
PROGRAM_ENTRY_POINT = 'PROGRAM_ENTRY_POINT'
PROGRAM_ENTRY_POINTS = 'PROGRAM_ENTRY_POINTS'
PROGRAM_TREES = 'PROGRAM_TREES'
PROPERTIES = 'PROPERTIES'
PROPERTY = 'PROPERTY'
REGISTER = 'REGISTER'
REGISTER_SAVE_SIZE = 'REGISTER_SAVE_SIZE'
REGISTER_VALUES = 'REGISTER_VALUES'
REGISTER_VALUE_RANGE = 'REGISTER_VALUE_RANGE'
REGISTER_VAR = 'REGISTER_VAR'
REGULAR_CMT = 'REGULAR_CMT'
RELOCATION = 'RELOCATION'
RELOCATION_TABLE = 'RELOCATION_TABLE'
REPEATABLE_CMT = 'REPEATABLE_CMT'
RETURN_ADDR_SIZE = 'RETURN_ADDR_SIZE'
RETURN_TYPE = 'RETURN_TYPE'
SHOW_TERMINATOR = 'SHOW_TERMINATOR'
SIGNED = 'SIGNED'
SIZE = 'SIZE'
SOURCE_ADDRESS = 'SOURCE_ADDRESS'
SOURCE_TYPE = 'SOURCE_TYPE'
STACK_FRAME = 'STACK_FRAME'
STACK_PTR_OFFSET = 'STACK_PTR_OFFSET'
STACK_REFERENCE = 'STACK_REFERENCE'
STACK_VAR = 'STACK_VAR'
START = 'START'
START_ADDR = 'START_ADDR'
START_ADDRESS = 'START_ADDRESS'
STRUCTURE = 'STRUCTURE'
SYMBOL = 'SYMBOL'
SYMBOL_TABLE = 'SYMBOL_TABLE'
TIMESTAMP = 'TIMESTAMP'
TOOL = 'TOOL'
TO_ADDRESS = 'TO_ADDRESS'
TREE = 'TREE'
TYPE = 'TYPE'
TYPEINFO_CMT = 'TYPEINFO_CMT'
TYPE_DEF = 'TYPE_DEF'
UNION = 'UNION'
USER = 'USER'
USER_DEFINED = 'USER_DEFINED'
VALUE = 'VALUE'
VARIABLE_LENGTH = 'VARIABLE_LENGTH'
ZERO_PAD = 'ZERO_PAD'


repository: ImpaleAskewArdently/ghidra_hex_bin
file: GhidraBuild/IDAPro/Python/6xx/plugins/xmlexp.py
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2024-08-01 03:15:40+00:00
pushed_at: 2024-08-01 04:30:29+00:00
url:
https://github.com/ImpaleAskewArdently/ghidra_hex_bin/blob/06ca3190d1d9519df8513489ce4f539f7be2af03/GhidraBuild/IDAPro/Python/6xx/plugins/xmlexp.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.
"""

import idaapi
import idautils
import idc
import datetime
import sys
import time


XML_EXPORTER_VERSION = "4.1.2"
BASELINE_IDA_VERSION = 620
IDA_SDK_VERSION = idaapi.IDA_SDK_VERSION
BADADDR = idaapi.BADADDR


class XmlExporterPlugin(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 IDA_SDK_VERSION < BASELINE_IDA_VERSION:
            idaapi.msg('\nXML Exporter plugin (xmlexp.py) not supported ' +
                        'by this version of IDA\n')
            return idaapi.PLUGIN_SKIP
        return idaapi.PLUGIN_OK


    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.
        """
        xml = XmlExporter(arg)
        try:
            try:
                xml.export_xml()
            except Cancelled:
                idaapi.hide_wait_box()
                msg = "XML Export cancelled!"
                print "\n" + msg
                idaapi.warning(msg)
            except:
                idaapi.hide_wait_box()
                msg = "***** Exception occurred: XML Exporter failed! *****"
                print "\n" + msg + "\n", sys.exc_type, sys.exc_value
                idaapi.warning(msg)
        finally:
            xml.cleanup()
            idaapi.setStat(xml.state)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlExporterPlugin()


class Cancelled(Exception):
    pass


class FileError(Exception):
    pass


class XmlExporter:
    """
    XML Exporter contains methods to export an IDA database as a
        XML PROGRAM document.
    """
    def __init__(self, arg):
        """
        Initializes the XmlExporter attributes

        Args:
            arg: Integer, non-zero value enables auto-run feature for
                IDA batch (no gui) processing mode. Default is 0.
        """
        self.debug = False
        self.autorun = False
        if arg != 0:
            self.autorun = True
        self.state = idaapi.setStat(idaapi.st_Work)
        self.indent_level = 0
        self.seg_addr = False
        self.has_overlays = False
        self.hexrays = False
        self.options = None
        self.xmlfile = 0
        self.elements = {}
        self.counters = []
        self.tags = []
        
        # initialize class variables from database
        self.inf = idaapi.get_inf_structure()
        self.min_ea = self.inf.minEA
        self.max_ea = self.inf.maxEA
        self.cbsize = (idaapi.ph_get_cnbits()+7)/8
        self.processor = str.upper(idaapi.get_idp_name())
        self.batch = idaapi.cvar.batch


    def export_xml(self):
        """
        Exports the IDA database to a XML PROGRAM document file.
        """
        self.check_and_load_decompiler()
        self.display_xml_exporter_version()
        self.display_database_info()
        
        self.get_options()
    
        if (self.autorun == True):
            (self.filename, ext) = os.path.splitext(idaapi.cvar.database_idb)
            self.filename += ".xml"
        else:
            self.filename=idaapi.askfile_c(1, "*.xml",
                                           "Enter name of export xml file:")
            
        if self.filename == None or len(self.filename) == 0:
            raise Cancelled
        self.xmlfile = self.open_file(self.filename, "w")
        
        idaapi.show_wait_box("Exporting XML <PROGRAM> document ....")
        idaapi.msg("\n------------------------------------------------" +
                   "-----------")
        idaapi.msg("\nExporting XML <PROGRAM> document ....")
        begin = time.clock()
        
        self.write_xml_declaration()
        self.export_program()
        
        # export database items based on options
        if (self.options.DataTypes.checked         == True or
            self.options.DataDefinitions.checked   == True or 
            self.options.Functions.checked         == True):
            self.export_datatypes()
        if (self.options.MemorySections.checked    == True or
            self.options.MemoryContent.checked     == True):
            self.export_memory_map()
        if (self.options.RegisterValues.checked    == True):
            self.export_register_values()   
        if (self.options.CodeBlocks.checked        == True):
            self.export_code()  
        if (self.options.DataDefinitions.checked   == True):
            self.export_data()  
        if (self.options.Comments.checked          == True):
            self.export_comments()
            self.export_bookmarks()     
        if (self.options.EntryPoints.checked       == True):
            self.export_program_entry_points()  
        if (self.options.Symbols.checked           == True):
            self.export_symbol_table()  
        if (self.options.Functions.checked         == True):
            self.export_functions() 
        if (self.options.MemoryReferences.checked  == True or 
            self.options.StackReferences.checked   == True or
            self.options.Manual.checked            == True or
            self.options.DataTypes.checked         == True):
            self.export_markup()    
        self.end_element(PROGRAM)
        
        idaapi.msg('\n%35s' % 'Total ')
        self.display_cpu_time(begin)
        idaapi.hide_wait_box()  
        self.display_summary()


    def check_and_load_decompiler(self):
        """
        Checks for the presence of a decompiler plugin for the database.
        
        Note: The decompiler must be loaded by the XML Exporter plugin
            if it is running in batch mode. IDA will load the decompiler
            plugin automatically if not in batch mode.
        
        Note: There was no support for decompiler plugins in IDAPython until
            IDA 6.6, so skip if this is an older version.
        
        Note: Currently the 4 decompiler plugins for the  x86, x64,
            ARM32, and ARM64 are supported.
        """
        if self.batch == 0 or IDA_SDK_VERSION < 660:
            return
        plugin = ''
        if self.processor == 'PC':
            if self.inf.is_64bit():
                plugin = "hexx64"
            elif self.inf.is_32bit():
                plugin = 'hexrays'
        elif self.processor == 'ARM':
            if self.inf.is_64bit():
                plugin = "hexarm64"
            elif self.inf.is_32bit():
                plugin = "hexarm"
        if len(plugin) > 0:
            try:
                idaapi.load_plugin(plugin)
                self.hexrays = idaapi.init_hexrays_plugin()
            except:
                return
    

    def check_char(self, ch):
        """
        Replaces a special XML character with an entity string.
        
        Args:
            ch: String containing the character to check.
            
        Returns:
            String containing either the character or the entity
            substition string.
        """
        if ((ord(ch) < 0x20) and (ord(ch) != 0x09 and
             ord(ch) != 0x0A and ord(ch) != 0x0D)): return ''
        elif ch == '&' :  return '&amp;'
        elif ch == '<' :  return "&lt;"
        elif ch == '>' :  return "&gt;"
        elif ch == '\'' : return "&apos;"
        elif ch == '"' :  return "&quot;"
        elif ch == '\x7F': return ''
        elif ord(ch) > 0x7F: return '&#x' + format(ord(ch),"x") + ";"
        return ch
    

    def check_for_entities(self, text):
        """
        Checks all characters in a string for special XML characters.
        
        Args:
            text: String to check for special XML characters.
            
        Returns:
            String containing original string with substitutions for
                any special XML characters.
        """
        new = ''
        for c in text:
            new += self.check_char(c)
        return new
    

    def check_if_seg_contents(self, seg):
        """
        Determines if any address in a segment contains a value.
        
        Args:
            seg: IDA segment object
            
        Returns:
            True if any address in a segment contains a value.
            False if no address in a segment contains a value.
        """
        for addr in idautils.Heads(seg.startEA, seg.endEA):
            if idaapi.hasValue(idaapi.getFlags(addr)) == True:
                return True
        return False


    def check_stack_frame(self, sframe):
        """
        Determines if stack frame contains any parameters or local variables.
        
        Args:
            sframe: IDA stack frame for a function.
            
        Returns:
            True if stack frame has parameters or local variables.
            False if stack frame has no parameters or local variables.
        """
        n = sframe.memqty
        for i in range(n):
            member = sframe.get_member(i)
            if member == None:
                continue
            mname = idaapi.get_member_name(member.id)
            if mname != None and len(mname) > 0: 
                if mname != " s" and mname != " r":
                    return True
        return False


    def cleanup(self):
        """
        Frees memory and closes message box and XML file at termination.
        """
        if self.options != None:
            self.options.Free()
        idaapi.hide_wait_box()
        self.close_xmlfile()


    def close_binfile(self):
        """
        Closes the binary data file for the XML Exporter.
        """
        if self.binfile != 0:
            self.binfile.close()
            self.binfile = 0


    def close_tag(self, has_contents=False):
        """
        Closes the start tag for an XML element.
        
        Args:
            has_contents: Boolean indicating if the element has
            sub-elements or text.
        """
        if has_contents:
            self.write_to_xmlfile(">")
            self.indent_level += 1
        else:
            self.write_to_xmlfile(" />")


    def close_xmlfile(self):
        """
        Closes the XML data file for the XML Exporter.
        """
        if self.xmlfile != 0:
            self.xmlfile.close()
            self.xmlfile = 0


    def dbg(self, message):
        """
        Outputs debug message if debug flag is enabled.
        
        Args:
            message: String containing the debug message.
        """
        if (self.debug == True):
            idaapi.msg(message)


    def display_cpu_time(self, start):
        """
        Displays the elapsed CPU time since the start time.
        
        Args:
            start: Floating-point value representing start time in seconds.
        """
        idaapi.msg('CPU time: %6.4f' % (time.clock() - start))
        

    def display_database_info(self):
        """
        Displays information about the database in IDA output window.
        """
        idaapi.msg('\nProcessor Module: ' + self.processor)


    def display_message(self, message):
        """
        Displays an indented message to IDA output window.
        
        Args:
            message: String containing message to display.
        """
        line = "    " + message
        idaapi.msg('\n%-35s' % line)
        

    def display_summary(self):
        """
        Displays summary of exported PROGRAM document in IDA output window.
        """
        fileline = '\nFile: %s' % self.filename
        summary  = "\n--------------------------------------"
        total = 0
        tags = self.tags[:]
        for tag in tags:
            count = self.counters[self.elements[tag]]
            summary += "\n%-22s %8d" % (tag, count)
            total += count
        summary += ("\n%-22s %8d" % ("Total XML Elements:",total))
        idaapi.msg(summary)
        idaapi.msg("\n\nDatabase exported to: %s" % self.filename)
        idaapi.msg("\n--------------------------------------------" +
                   "---------------")
        if self.autorun == False:
            frmt  = "TITLE XML Export Successful!\n"
            frmt += "ICON INFO\n"
            frmt += "AUTOHIDE NONE\n"
            frmt += "HIDECANCEL\n"
            details = '\nSee output window for details...'
            idaapi.info("%s" % (frmt + fileline + details))


    def display_xml_exporter_version(self):
        """
        Displays XML Exporter plugin version info in IDA output window.
        """
        plugin = idaapi.idadir(idaapi.PLG_SUBDIR) + '/xmlexp.py'
        plugintime = time.localtime(os.path.getmtime(plugin))
        # ts = time.strftime('%Y-%m-%d %H:%M:%S', plugintime)
        ts = time.strftime('%b %d %Y %H:%M:%S', plugintime)
        version = "\nXML Exporter Version " + XML_EXPORTER_VERSION
        version += " : SDK " + str(IDA_SDK_VERSION)
        version += " : Python : "+ ts + '\n'
        idaapi.msg(version)
    

    def end_element(self, tag, newline=True):
        """
        Writes the element end tag to the XML file.
        
        Args:
            tag: String containing the element name.
            newline: Boolean indicating if end tag should go on new line.
        """
        self.indent_level -= 1
        if newline:
            start = '\n' + ("    " * self.indent_level)
        else:
            start = ''
        self.write_to_xmlfile(start  + "</" + tag + ">")


    '''
    # BIT_MASK not currently supported for ENUM
    def export_bitmask(self, eid, mask):
        """
        Exports an enum bitmask member as BIT_MASK element.
        
        Args:
            eid: Integer representing the IDA enum id
            mask: Integer representing the IDA enum mask value
        """
        name = idaapi.get_bmask_name(eid, mask)
        if name == None:
            return
        self.start_element(BIT_MASK)
        self.write_attribute(NAME, name)
        self.write_numeric_attribute(VALUE, mask)
        regcmt = idaapi.get_bmask_cmt(eid, mask, False)
        rptcmt = idaapi.get_bmask_cmt(eid, mask, True)
        has_comment =  regcmt != None or rptcmt != None
        self.close_tag(has_comment)
        if regcmt != None and len(regcmt) > 0:
            self.export_regular_cmt(regcmt)
        if rptcmt != None and len(rptcmt) > 0:
            self.export_repeatable_cmt(rptcmt)
        if (has_comment):
            self.end_element(BIT_MASK)
    '''


    def export_bookmarks(self):
        """
        Exports marked location descriptions as BOOKMARK elements.
        """
        found = False
        timer = time.clock()
        if IDA_SDK_VERSION > 690:
            import ida_moves
            import ida_pro
        for slot in range(1,1025):
            if IDA_SDK_VERSION <= 690:
                address = idc.GetMarkedPos(slot)
                description = idc.GetMarkComment(slot)
            else:
                curloc = ida_moves.curloc()
                intp = ida_pro.int_pointer()
                intp.assign(slot)
                address = curloc.markedpos(intp)
                description = curloc.markdesc(slot)
            if address == BADADDR:
                continue
            if description == None:
                continue
            if found == False:
                found = True
                self.update_status(BOOKMARKS)
                self.start_element(BOOKMARKS, True)
            self.start_element(BOOKMARK)
            self.write_address_attribute(ADDRESS, address)
            self.write_attribute(DESCRIPTION, description)
            self.close_tag()
        if found:
            self.end_element(BOOKMARKS)
            self.display_cpu_time(timer)


    def export_c_comments(self):
        """
        Exports block and end-of-line comments entered in the decompiler
        interface.
        """
        if IDA_SDK_VERSION < 660 or self.hexrays == False:
            return
        functions = idautils.Functions()
        if functions == None:
            return
        for addr in functions:
            try:
                if idaapi.is_spec_ea(addr):
                    continue
                ccmts = idaapi.restore_user_cmts(addr)
                if ccmts == None:
                    continue
                p = idaapi.user_cmts_begin(ccmts)
                while p != idaapi.user_cmts_end(ccmts):
                    cmk = idaapi.user_cmts_first(p)
                    cmv = idaapi.user_cmts_second(p)
                    if cmk.itp < (idaapi.ITP_COLON+1):
                        self.export_comment(cmk.ea, "end-of-line", cmv.c_str())
                    else:
                        self.export_comment(cmk.ea, "pre", cmv.c_str())
                    p=idaapi.user_cmts_next(p)
                idaapi.user_cmts_free(ccmts)
            except:
                continue


    def export_code(self):
        """
        Exports the address ranges of code sequences as CODE_BLOCK(s)
        with START and END address attributes.
        """
        addr = self.min_ea
        if idaapi.isCode(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.isCode)
        if (addr == BADADDR):
            return
        self.update_status(CODE)
        timer = time.clock()
        data = idaapi.nextthat(addr, self.inf.maxEA, isData)
        unknown = idaapi.next_unknown(addr, self.inf.maxEA)
        self.start_element(CODE, True)
        while (addr != BADADDR):
            start = addr
            end = min(data, unknown)
            if (end == BADADDR):
                if (idaapi.getseg(start).endEA < self.inf.maxEA):
                    codeend = idaapi.getseg(start).endEA - 1
                    addr = idaapi.getseg(idaapi.nextaddr(codeend)).startEA
                    if idaapi.isCode(idaapi.getFlags(addr)) == False:
                        addr = idaapi.nextthat(addr, self.max_ea,
                                               idaapi.isCode)
                else:
                    codeend = self.max_ea - 1
                    addr = BADADDR
            else:
                if (idaapi.getseg(start).endEA < end):
                    codeend = idaapi.getseg(start).endEA - 1
                    addr = idaapi.getseg(idaapi.nextaddr(codeend)).startEA
                    if idaapi.isCode(idaapi.getFlags(addr)) == False:
                        addr = idaapi.nextthat(addr, self.max_ea,
                                               idaapi.isCode)
                else:
                    codeend = idaapi.get_item_end(idaapi.prevthat(end,
                                                start, idaapi.isCode)) - 1
                    addr = idaapi.nextthat(end, self.max_ea,
                                           idaapi.isCode)
                if (data < addr):
                    data = idaapi.nextthat(addr, self.max_ea,
                                           idaapi.isData)
                if (unknown < addr):
                    unknown = idaapi.next_unknown(addr, self.max_ea)
            self.start_element(CODE_BLOCK)
            self.write_address_attribute(START, start)
            self.write_address_attribute(END, codeend)
            self.close_tag()
        self.end_element(CODE)
        self.display_cpu_time(timer)


    def export_comment(self, addr, cmt_type, cmt):
        """
        Exports a <COMMENT> element with ADDRESS and TYPE attributes.
        The comment is exported as the element text (parsed character data).
        
        Args:
            addr: Integers representing address of comment.
            cmt_type: String indicating the comment type.
            cmt: String containing the comment.
        """
        self.start_element(COMMENT)
        self.write_address_attribute(ADDRESS, addr)
        self.write_attribute(TYPE, cmt_type)
        self.close_tag(True)
        # tag_remove seems to be losing last character
        # work around is to add a space
        cmt_text = idaapi.tag_remove(cmt + ' ')
        self.write_text(cmt_text)
        self.end_element(COMMENT, False)


    def export_comments(self):
        """
        Exports all comments in the IDA database as <COMMENT> elements.
        """
        addr = self.min_ea
        if idaapi.has_cmt(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.has_cmt)
        if (addr == BADADDR):
            return
        self.update_status(COMMENTS)
        timer = time.clock()
        self.start_element(COMMENTS, True)
        while (addr != BADADDR):
            cmt = idaapi.get_cmt(addr, False)
            if (cmt != None):
                self.export_comment(addr, "end-of-line", cmt)
            cmt = idaapi.get_cmt(addr, True)
            if (cmt != None):
                self.export_comment(addr, "repeatable", cmt)
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.has_cmt)
        addr = self.min_ea
        if idaapi.hasExtra(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.hasExtra)
        while (addr != BADADDR):
            if IDA_SDK_VERSION < 640:
                extra = idaapi.ExtraGet(addr, idaapi.E_PREV)
            else:
                extra = idaapi.get_extra_cmt(addr, idaapi.E_PREV)
            if (extra != None):
                self.export_extra_comment(addr, "pre", idaapi.E_PREV)
            if IDA_SDK_VERSION < 640:
                extra = idaapi.ExtraGet(addr, idaapi.E_NEXT)
            else:
                extra = idaapi.get_extra_cmt(addr, idaapi.E_NEXT)
            if (extra != None):
                self.export_extra_comment(addr, "post", idaapi.E_NEXT)
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.hasExtra)
        self.export_c_comments()
        self.end_element(COMMENTS)
        self.display_cpu_time(timer)


    def export_data(self):
        """
        Exports the data items in the database as <DEFINED_DATA> elements.
        """
        addr = self.min_ea
        if idaapi.isData(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.isData)
        if (addr == BADADDR):
            return
        timer = time.clock()
        self.update_status(DATA)
        self.start_element(DATA, True)
        while (addr != BADADDR):
            f = idaapi.getFlags(addr)
            if idaapi.isAlign(f) == True:
                addr = idaapi.nextthat(addr, self.max_ea, idaapi.isData)
                continue
            dtype = self.get_datatype(addr)
            size = idaapi.get_item_size(addr)
            ti = idaapi.opinfo_t()
            if IDA_SDK_VERSION < 640:
                msize = idaapi.get_data_elsize(addr, f, ti)
            else:
                msize = idaapi.get_data_type_size(f, ti)
            if idaapi.isStruct(f) == True:
                s = idaapi.get_struc_id(dtype)
                msize = idaapi.get_struc_size(s)
                if msize == 0:
                    msize = 1
            if idaapi.isASCII(f) == False and size != msize:
                dtype = "%s[%d]" % (dtype, size/msize)
            self.start_element(DEFINED_DATA)
            self.write_address_attribute(ADDRESS, addr)
            self.write_attribute(DATATYPE, dtype)
            self.write_numeric_attribute(SIZE, size*self.cbsize)
            #TODO consider using GetTrueNameEx and Demangle
            demangled = idaapi.get_demangled_name(BADADDR, addr,
                            DEMANGLED_TYPEINFO, self.inf.demnames, True)
            outbuf = ''
            if IDA_SDK_VERSION < 660:
                outbuf = idaapi.print_type(addr, False)
            else:
                outbuf = idaapi.print_type(addr, outbuf)
            if demangled == "'string'":
                demangled == None
            has_typeinfo = demangled != None or (outbuf != None and
                                                len(outbuf) > 0)
            #TODO export_data: add DISPLAY_SETTINGS
            self.close_tag(has_typeinfo)
            if has_typeinfo == True:
                if demangled != None:
                    self.export_typeinfo_cmt(demangled)
                else:
                    #TODO export_data - check this
                    self.export_typeinfo_cmt(outbuf[:-1])
                self.end_element(DEFINED_DATA)
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.isData)
        self.end_element(DATA)
        self.display_cpu_time(timer)
        

    def export_datatypes(self):
        """
        Exports the structures and enums in IDA database.
        """
        # skip if no structures/unions to export
        if idaapi.get_struc_qty() == 0: return
        self.update_status(DATATYPES)
        timer = time.clock()
        self.start_element(DATATYPES, True)
        self.export_structures()
        self.export_enums()
        self.end_element(DATATYPES)
        self.display_cpu_time(timer)


    def export_enum_member(self, cid, bf, mask, radix, signness):
        """
        Exports a member of an enum.

        This function can only be called by IDA versions newer than 6.3 
        
        Args:
            cid: Integer representing id of enum member
            bf: Boolean indicates if a bitfield
            mask: Integer representing bitmask if bitfield
            radix: Integer representing numeric display format
            signness: Boolean indicating if signed value 
        """
        if IDA_SDK_VERSION < 640:
            return
        cname = idaapi.get_const_name(cid)
        if cname == None or len(cname) == 0:
            return
        regcmt = idaapi.get_const_cmt(cid, False)
        rptcmt = idaapi.get_const_cmt(cid, True)
        has_comment =  regcmt != None or rptcmt != None
        self.start_element(ENUM_ENTRY)
        self.write_attribute(NAME, cname)
        value = idaapi.get_const_value(cid)
        self.write_numeric_attribute(VALUE, value, radix, signness)
        # BIT_MASK attribute not currently supported for ENUM_ENTRY
        #if bf == True:
        #    self.write_numeric_attribute(BIT_MASK, mask)
        self.close_tag(has_comment)
        if regcmt != None and len(regcmt) > 0:
            self.export_regular_cmt(regcmt)
        if rptcmt != None and len(rptcmt) > 0:
            self.export_repeatable_cmt(rptcmt)
        if (has_comment):
            self.end_element(ENUM_ENTRY)


    def export_enum_member_legacy(self, cid, bf, mask, radix, signness):
        """
        Exports a member of an enum.
        
        This function can only be called by IDA versions older than 6.3 

        Args:
            cid: Integer representing id of enum member
            bf: Boolean indicates if a bitfield
            mask: Integer representing bitmask if bitfield
            radix: Integer representing numeric display format
            signness: Boolean indicating if signed value 
        """
        if IDA_SDK_VERSION > 630:
            return
        cname = idaapi.get_enum_member_name(cid)
        if cname == None or len(cname) == 0:
            return
        regcmt = idaapi.get_enum_member_cmt(cid, False)
        rptcmt = idaapi.get_enum_member_cmt(cid, True)
        has_comment =  regcmt != None or rptcmt != None
        self.start_element(ENUM_ENTRY)
        self.write_attribute(NAME, cname)
        value = idaapi.get_enum_member_value(cid)
        self.write_numeric_attribute(VALUE, value, radix, signness)
        # BIT_MASK attribute not currently supported for ENUM_ENTRY
        #if bf == True:
        #    self.write_numeric_attribute(BIT_MASK, mask)
        self.close_tag(has_comment)
        if regcmt != None and len(regcmt) > 0:
            self.export_regular_cmt(regcmt)
        if rptcmt != None and len(rptcmt) > 0:
            self.export_repeatable_cmt(rptcmt)
        if (has_comment):
            self.end_element(ENUM_ENTRY)


    def export_enum_members(self, eid, bf, eflags):
        """
        Exports the members of an enum.

        This function can only be called by IDA versions newer than 6.3 
        
        Args:
            eid: Integer representing id of enum
            bf: Boolean indicates if a bitfield
            eflags: Integer representing the enum flags
        """
        if IDA_SDK_VERSION < 640:
            return
        mask=0xFFFFFFFF
        if bf == True:
            mask = idaapi.get_first_bmask(eid)
        first = True
        for n in range(idaapi.get_enum_size(eid)):
            if (first == True):
                value = idaapi.get_first_const(eid, mask)
                first = False
            else:
                value = idaapi.get_next_const(eid, value, mask)
            (cid, serial) = idaapi.get_first_serial_const(eid, value, mask)
            main_cid = cid
            while cid != idaapi.BADNODE:
                self.export_enum_member(cid, bf, mask,
                                   idaapi.getRadix(eflags, 0),
                                   self.is_signed_data(eflags))
                last_value = idaapi.get_last_const(eid, mask)
                if value == last_value:
                    # BIT_MASK not currently supported for ENUM
                    #self.export_bitmask(eid, mask)
                    mask = idaapi.get_next_bmask(eid, mask)
                    first = True
                (cid, serial) = idaapi.get_next_serial_const(main_cid)


    def export_enum_members_legacy(self, eid, bf, eflags):
        """
        Exports the members of an enum.
        
        This function can only be called by IDA versions older than 6.3 
        
        Args:
            eid: Integer representing id of enum
            bf: Boolean indicates if a bitfield
            eflags: Integer representing the enum flags
        """
        if IDA_SDK_VERSION > 630:
            return
        mask=0xFFFFFFFF
        if bf == True:
            mask = idaapi.get_first_bmask(eid)
        first = True
        for n in range(idaapi.get_enum_size(eid)):
            if (first == True):
                value = idaapi.get_first_enum_member(eid, mask)
                first = False
            else:
                value = idaapi.get_next_enum_member(eid, value, mask)
            (cid, serial) = idaapi.get_first_serial_enum_member(eid, value, mask)
            main_cid = cid
            while cid != idaapi.BADNODE:
                self.export_enum_member_legacy(cid, bf, mask,
                                   idaapi.getRadix(eflags, 0),
                                   self.is_signed_data(eflags))
                last_value = idaapi.get_last_enum_member(eid, mask)
                if value == last_value:
                    # BIT_MASK not currently supported for ENUM
                    #self.export_bitmask(eid, mask)
                    mask = idaapi.get_next_bmask(eid, mask)
                    first = True
                (cid, serial) = idaapi.get_next_serial_enum_member(main_cid)


    def export_enum_reference(self, addr, op):
        """
        Exports the enum reference for an operand at an address.
        
        Args:
            addr: Integer representing the instruction address.
            op: Integer representing the operand index (0-based)
        """
        (eid, serial) = idaapi.get_enum_id(addr, op)
        idaapi.decode_insn(addr)
        value = idaapi.cmd.Operands[op].value
        cid = idaapi.BADNODE
        last = idaapi.get_last_bmask(eid)
        if idaapi.is_bf(eid) == True:
            last = idaapi.get_last_bmask(eid)
            mask = idaapi.get_first_bmask(eid)
            while  (cid == idaapi.BADNODE):
                if IDA_SDK_VERSION < 640:
                    cid = idaapi.get_enum_member(eid, (value & mask), 0, mask)
                else:
                    cid = idaapi.get_const(eid, (value & mask), 0, mask)
                if cid != idaapi.BADNODE or mask == last:
                    break
                mask = idaapi.get_next_bmask(eid, mask)
        else:
            if IDA_SDK_VERSION < 640:
                cid = idaapi.get_enum_member(eid, value, 0, last)
            else:
               cid = idaapi.get_const(eid, value, 0, last)
        if (cid == idaapi.BADNODE):
            return
        self.start_element(EQUATE_REFERENCE)
        self.write_address_attribute(ADDRESS, addr)
        self.write_numeric_attribute(OPERAND_INDEX, op, 10)
        if IDA_SDK_VERSION < 640:
            self.write_numeric_attribute(VALUE, idaapi.get_enum_member_value(cid))
            cname = idaapi.get_enum_member_name(cid)
        else:
            self.write_numeric_attribute(VALUE, idaapi.get_const_value(cid))
            cname = idaapi.get_const_name(cid)
        if cname != None and len(cname) > 0:
            self.write_attribute(NAME, cname)
        # BIT_MASK feature not currently supported
        #if idaapi.is_bf(eid) == True:
        #    self.write_numeric_attribute("BIT_MASK", mask);
        self.close_tag()
        

    def export_enum_references(self, addr):
        """
        Finds and exports enum references at an address.
        
        Args:
            addr: Integer representing the instruction address.
        """
        f = idaapi.getFlags(addr)
        for op in range(2):
            if idaapi.isEnum(f, op) == True:
                self.export_enum_reference(addr, op)
                

    def export_enums(self):
        """
        Exports enumerations.
        """
        num_enums = idaapi.get_enum_qty()
        if (num_enums == 0):
            return
        for i in range(num_enums):
            self.start_element(ENUM)
            eid = idaapi.getn_enum(i)
            ename = idaapi.get_enum_name(eid)
            if (ename == None or len(ename) == 0):
                continue
            self.write_attribute(NAME, ename)
            ewidth = idaapi.get_enum_width(eid)
            if ewidth != 0 and ewidth <= 7:
                self.write_numeric_attribute(SIZE, 1 << (ewidth-1), 10)
            eflags = idaapi.get_enum_flag(eid)
            bf = idaapi.is_bf(eid)
            # BIT_FIELD attribute not supported for ENUM
            #if bf == True:
            #    self.write_attribute(BIT_FIELD, "yes")
            regcmt = idaapi.get_enum_cmt(eid, False)
            rptcmt = idaapi.get_enum_cmt(eid, True)
            has_children = ((idaapi.get_enum_size(eid) > 0) or
                            (regcmt != None) or (rptcmt != None) or
                            (idaapi.getRadix(eflags, 0) != 16) or
                            (self.is_signed_data(eflags) == True))
            self.close_tag(has_children)
            if (idaapi.getRadix(eflags, 0) != 16 or
                self.is_signed_data(eflags) == True):
                self.start_element(DISPLAY_SETTINGS)
                if idaapi.getRadix(eflags, 0) != 16:
                    self.write_attribute(FORMAT, self.get_format(eflags))
                if self.is_signed_data(eflags) == True:
                    self.write_attribute(SIGNED, "yes")
                self.close_tag()
            if regcmt != None:
                self.export_regular_cmt(regcmt)
            if rptcmt != None:
                self.export_repeatable_cmt(rptcmt)
            if IDA_SDK_VERSION < 640:
                self.export_enum_members_legacy(eid, bf, eflags)
            else:
                self.export_enum_members(eid, bf, eflags)
            if (has_children):
                self.end_element(ENUM)


    def export_extra_comment(self, addr, cmt_type, extra):
        """
        Exports pre- and post- comments for an address.
        
        Args:
            addr: Integer representing the instruction address.
            cmt_type: String indicating comment type
            extra: Integer representing extra comment index
        """
        cmt = ''
        if IDA_SDK_VERSION < 640:
            nextline = idaapi.ExtraGet(addr, extra)
        else:
            nextline = idaapi.get_extra_cmt(addr, extra)
        while (nextline != None):
            # workaround for tag_remove bug is to add space
            cmt += idaapi.tag_remove(nextline + ' ')
            extra += 1
            if IDA_SDK_VERSION < 640:
                nextline = idaapi.ExtraGet(addr, extra)
            else:
                nextline = idaapi.get_extra_cmt(addr, extra)
            if (nextline != None):
                cmt += '\n' 
        self.export_comment(addr, cmt_type, cmt)


    def export_functions(self):
        """
        Exports information about all functions. 
        """
        functions = idautils.Functions()
        if functions == None:
            return
        self.update_status(FUNCTIONS)
        timer = time.clock()
        self.start_element(FUNCTIONS, True)
        for addr in functions:
            function = idaapi.get_func(addr)
            if idaapi.is_spec_ea(function.startEA) == True:
                continue
            self.start_element(FUNCTION)
            self.write_address_attribute(ENTRY_POINT, function.startEA)
            if idaapi.has_user_name(idaapi.getFlags(addr)) == True:
                name = self.get_symbol_name(addr)
                if name != None and len(name) > 0:
                    self.write_attribute(NAME, name)
            if function.flags & idaapi.FUNC_LIB != 0:
                self.write_attribute(LIBRARY_FUNCTION, "y")
            self.close_tag(True)
            fchunks = idautils.Chunks(addr)
            for (startEA, endEA) in fchunks:
                self.start_element(ADDRESS_RANGE)
                self.write_address_attribute(START, startEA)
                self.write_address_attribute(END, endEA-1)
                self.close_tag()
            regcmt = idaapi.get_func_cmt(function, False)
            if regcmt != None:
                self.export_regular_cmt(regcmt)
            rptcmt = idaapi.get_func_cmt(function, True)
            if rptcmt != None:
                self.export_repeatable_cmt(rptcmt)
            demangled = idaapi.get_demangled_name(BADADDR, addr,
                                            DEMANGLED_TYPEINFO,
                                            self.inf.demnames, True)
            if demangled != None and demangled == "'string'":
                demangled = None
            outbuf = ''
            if IDA_SDK_VERSION < 660:
                outbuf = idaapi.print_type(addr, False)
            else:
                outbuf = idaapi.print_type(addr, outbuf)
            has_typeinfo = (demangled != None or (outbuf != None and
                            len(outbuf) > 0))
            if demangled != None:
                self.export_typeinfo_cmt(demangled)
            elif has_typeinfo == True:
                self.export_typeinfo_cmt(outbuf[:-1])
            self.export_stack_frame(function)
            self.end_element(FUNCTION)
        self.end_element(FUNCTIONS)
        self.display_cpu_time(timer)


    def export_manual_instruction(self, addr):
        """
        Exports user-entered "manual instruction" at an address.
        
        Args:
            addr: Integer representing instruction address.
        """
        text = idaapi.get_manual_insn(addr)
        if text == None or len(text) == 0:
            return
        self.start_element(MANUAL_INSTRUCTION)
        self.write_address_attribute(ADDRESS, addr)
        self.close_tag(True)
        self.write_text(text)
        self.end_element(MANUAL_INSTRUCTION, False)
        

    def export_manual_operand(self, addr):
        """
        Exports user-entered "manual operands" at an address.
        
        Args:
            addr: Integer representing instruction address.
        """
        for op in range(idaapi.UA_MAXOP):
            if idaapi.is_forced_operand(addr, op) == True:
                text = idaapi.get_forced_operand(addr, op)
                if text != None and len(text) > 0:
                    self.start_element(MANUAL_OPERAND)
                    self.write_address_attribute(ADDRESS, addr)
                    self.write_numeric_attribute(OPERAND_INDEX, op, 10)
                    self.close_tag(True)
                    self.write_text(text)
                    self.end_element(MANUAL_OPERAND, False)


    def export_markup(self):
        """
        Exports markup for instructions and data items including references
        and manual instructions and operands.
        """
        self.update_status(MARKUP)
        timer = time.clock()
        self.start_element(MARKUP, True)
        addr = self.min_ea
        while addr != BADADDR:
            f = idaapi.getFlags(addr)
            if self.options.MemoryReferences.checked == True:
                if idaapi.hasRef(f) == True:
                    self.export_user_memory_reference(addr)
                if idaapi.isOff(f, idaapi.OPND_ALL) == True:
                    self.export_memory_references(addr)
            if (self.options.Functions.checked == True and
               self.options.StackReferences.checked == True and
               idaapi.isStkvar(f, idaapi.OPND_ALL) == True):
               self.export_stack_reference(addr)
            if (self.options.DataTypes.checked == True and
                    idaapi.isEnum(f, idaapi.OPND_ALL) == True):
                self.export_enum_references(addr)
            if self.options.Manual.checked == True:
                if idaapi.isFop(f, idaapi.OPND_ALL)   == True:
                    self.export_manual_operand(addr)
                if idaapi.is_manual_insn(addr) == True:
                    self.export_manual_instruction(addr)
            addr = idaapi.next_head(addr, self.max_ea)
        self.end_element(MARKUP)
        self.display_cpu_time(timer)


    def export_members(self, s):
        """
        Exports the members of a structure or union.
        
        Args:
            s: IDA structure/union instance
        """
        nmembers = s.memqty
        for n in range(nmembers):
            m = s.get_member(n)
            offset = m.soff
            if s.is_union() == True:
                offset = 0
            self.start_element(MEMBER)
            self.write_numeric_attribute(OFFSET, offset)
            mname = idaapi.get_member_name(m.id)
            if len(mname) > 0:
                self.write_attribute(NAME, mname)
            dtype = self.get_member_type(m)
            if idaapi.is_varmember(m) == True:
                msize = 0
                size  = 0
            else:
                mtibuf = idaapi.opinfo_t()
                mti = idaapi.retrieve_member_info(m, mtibuf)
                if IDA_SDK_VERSION < 640:
                    msize = idaapi.get_type_size0(None, dtype)
                    if msize == None or msize == 0:
                        msize = idaapi.get_member_size(m)
                else:
                    msize = idaapi.get_data_type_size(m.flag, mtibuf)
                size = idaapi.get_member_size(m)
                osize = size
                if size < msize: size = msize
            if (size != msize):
                arraytype = self.get_member_type(m)
                dtype = "%s[%d]" % (arraytype, size/msize)
            self.write_attribute(DATATYPE, dtype)
            self.write_numeric_attribute(SIZE, size*self.cbsize)
            regcmt = idaapi.get_member_cmt(m.id, False)
            rptcmt = idaapi.get_member_cmt(m.id, True)
            hascmt = regcmt != None or rptcmt != None
            self.close_tag(hascmt)
            if (hascmt):
                if regcmt != None:
                    self.export_regular_cmt(regcmt)
                if rptcmt != None:
                    self.export_repeatable_cmt(rptcmt)
                self.end_element(MEMBER)


    def export_memory_contents(self, binfilename, binfile, start, end):
        """
        Exports the binary memory contents in the database.
        
        A MEMORY_CONTENTS element is generated for each contiguous address
        range where each address in the range contains a value.
        The binary values are store in a separate file (not the XML file),
        and the MEMORY_CONTENTS element identifies the file and the
        offset in the file where the address range is located.

        Args:
            binfilename: String containing the absolute filepath
            binfile: IDA file instance for binary file
            start: Integer representing the starting address
            end: Integer representing the ending address
        """
        length = 0
        startaddr = start
        for addr in range(start, end):
            # reset start address when length == 0
            if (length == 0):
                startaddr = addr
            has_val = idaapi.hasValue(idaapi.getFlags(addr))
            if has_val == True:
                length += self.cbsize
            next_address = idaapi.nextaddr(addr)
            if ((has_val == False) or (next_address != addr+1) or
                    (next_address == end)):
                if length > 0:
                    offset = binfile.tell()
                    idaapi.base2file(binfile.get_fp(), offset, startaddr,
                                     startaddr+length)
                    self.start_element(MEMORY_CONTENTS)
                    self.write_address_attribute(START_ADDR, startaddr)
                    self.write_attribute(FILE_NAME, binfilename)
                    self.write_numeric_attribute(FILE_OFFSET, offset)
                    self.write_numeric_attribute(LENGTH, length)
                    self.close_tag(False)
                    length=0


    def export_memory_map(self):
        """
        Exports information about all memory blocks in the database.
        
        A MEMORY_SECTION is generated for each block (segment). If the
        memory block is initialized (has values), the contents are exported
        using the MEMORY_CONTENTS element.
        """
        nsegs = idaapi.get_segm_qty()
        if (nsegs == 0):
            return
        self.update_status(MEMORY_MAP)
        timer = time.clock();
        binfilename = ''
        if (self.options.MemoryContent.checked == True):
            (binfilename, ext) = os.path.splitext(self.filename)
            binfilename += ".bytes"
            self.binfile = idaapi.qfile_t()
            self.binfile.open(binfilename,'wb');
        self.start_element(MEMORY_MAP, True)
        for i in range(nsegs):
            self.export_memory_section(idaapi.getnseg(i), binfilename)
        self.end_element(MEMORY_MAP)
        if (self.options.MemoryContent.checked == True):
            self.close_binfile()
        self.display_cpu_time(timer)


    def export_memory_reference(self, addr, op):
        """
        Exports the memory reference for operand at the address.
        
        Args:
            addr: Integer representing the instruction address.
            op: Integer representing the operand index (0-based)
        """
        f = idaapi.getFlags(addr)
        ri = idaapi.refinfo_t()
        if idaapi.get_refinfo(addr, op, ri) == 1: 
            if ri.target != BADADDR:
                target = ri.target
            elif idaapi.isCode(f) == True:
                idaapi.decode_insn(addr)
                target = idaapi.cmd.Operands[op].value - ri.tdelta + ri.base
            elif idaapi.isData(f) == True:
                target = self.get_data_value(addr) - ri.tdelta + ri.base;
            else:
                return
        else:
            return
        if idaapi.isEnabled(target) == False:
            return
        self.start_element(MEMORY_REFERENCE)
        self.write_address_attribute(ADDRESS, addr)
        self.write_numeric_attribute(OPERAND_INDEX, op, 10)
        self.write_address_attribute(TO_ADDRESS, target)
        self.write_attribute(PRIMARY, "y")
        self.close_tag()
        

    def export_memory_references(self, addr):
        """
        Exports the memory references for any operands at the address.
        
        Args:
            addr: Integer representing the instruction address.
        """
        f = idaapi.getFlags(addr)
        for op in range(idaapi.UA_MAXOP):
            if idaapi.isOff(f, op) == True and (idaapi.isData(f) == True or
                    (idaapi.isCode(f) == True and
                    self.is_imm_op(addr, op) == True)):
                self.export_memory_reference(addr, op)
    

    def export_memory_section(self, seg, binfilename):
        """
        Exports segment information as a MEMORY_SECTIONS element.
        
        Args:
            seg: IDA segment instance
            binfilename: String containing absolute filepath for binary file.
        """
        segname = idaapi.get_segm_name(seg)
        self.start_element(MEMORY_SECTION)
        self.write_attribute(NAME, segname)
        self.write_address_attribute(START_ADDR, seg.startEA)
        length = (seg.endEA - seg.startEA)*self.cbsize
        self.write_numeric_attribute(LENGTH, length)
        perms = ""
        if (seg.perm != 0):
            if (seg.perm & idaapi.SEGPERM_READ  != 0):
                perms += 'r'
            if (seg.perm & idaapi.SEGPERM_WRITE != 0):
                perms += 'w' 
            if (seg.perm & idaapi.SEGPERM_EXEC  != 0):
                perms += 'x'
            if (len(perms) > 0):
                self.write_attribute(PERMISSIONS, perms)
        has_contents = (self.options.MemoryContent.checked == True and
                       self.check_if_seg_contents(seg) == True)
        self.close_tag(has_contents)
        if (has_contents == True):
            self.export_memory_contents(os.path.basename(binfilename),
                                      self.binfile, seg.startEA, seg.endEA)
            self.end_element(MEMORY_SECTION)


    def export_program(self):
        """
        Exports basic information about the program as the PROGRAM,
        INFO_SOURCE, PROCESSOR, and COMPILER elements.
        """
        # output the PROGRAM element
        self.update_status(PROGRAM);
        timer = time.clock()
        self.start_element(PROGRAM)
        self.write_attribute(NAME, idaapi.get_root_filename())
        self.write_attribute(EXE_PATH, idaapi.get_input_file_path())
        etype = idaapi.get_file_type_name()
        if (len(etype) > 0):
            self.write_attribute(EXE_FORMAT, etype)
        # check for presence of INPUT_MD5 netnode
        md5 = idaapi.netnode(INPUT_MD5)
        if md5 == idaapi.BADNODE:
            input_md5 = idc.GetInputMD5()
        else:
            input_md5 = md5.supval(idaapi.RIDX_MD5)
        if input_md5 != None:
            self.write_attribute(INPUT_MD5,input_md5)
        self.close_tag(True)
    
        # output the INFO_SOURCE element
        self.start_element(INFO_SOURCE)
        if IDA_SDK_VERSION < 670:
            tool  = 'IDA-Pro XML plugin (Python) SDK '
            tool +=  str(IDA_SDK_VERSION)
        else:
            tool  = 'IDA-Pro ' + idaapi.get_kernel_version()
            tool += ' XML plugin (Python) SDK ' + str(IDA_SDK_VERSION)
        self.write_attribute(TOOL, tool)
        user = os.getenv("USERNAME", "UNKNOWN")
        if (user == "UNKNOWN"):
            user = os.getenv("USER", "UNKNOWN")
        self.write_attribute(USER, user)
        self.write_attribute(FILE, idaapi.cvar.database_idb)
        ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        self.write_attribute(TIMESTAMP, ts)
        self.close_tag()
    
        # output the PROCESSOR element
        self.start_element(PROCESSOR)
        self.write_attribute(NAME, self.inf.procName)
        if (self.inf.mf):
            byte_order ="big"
        else:
            byte_order ="little"
        self.write_attribute(ENDIAN, byte_order)
        self.seg_addr = False
        bitness = 1
        model_warning = False
        nsegs = idaapi.get_segm_qty()
        if (nsegs > 0):
            bitness = idaapi.getnseg(0).bitness
            for i in range(1,nsegs):
                seg = idaapi.getnseg(i)
                if (seg.bitness != bitness):
                    model_warning = True
                if (seg.bitness > bitness):
                    bitness = seg.bitness
        addr_model = "32-bit"
        if (bitness == 0):
            addr_model = "16-bit"
        elif (bitness == 2):
            addr_model = "64-bit"
        self.write_attribute(ADDRESS_MODEL, addr_model)
        self.close_tag()
        if (model_warning):
            idaapi.msg("WARNING: Segments do not have same " +
                       "addressing model!\n")
        if (idaapi.ph.id == idaapi.PLFM_386 and bitness == 0):
            self.seg_addr = True
        # find any overlayed memory before processing addressable items
        self.find_overlay_memory()
    
        # output compiler info
        self.start_element(COMPILER)
        self.write_attribute(NAME, idaapi.get_compiler_name(self.inf.cc.id))
        self.close_tag()
        self.display_cpu_time(timer)


    def export_program_entry_points(self):
        """
        Exports entry points for the program.
        """
        nepts = idaapi.get_entry_qty()
        if (nepts  == 0):
            return
        self.update_status(PROGRAM_ENTRY_POINTS)
        timer = time.clock()
        self.start_element(PROGRAM_ENTRY_POINTS, True)
        for i in range(nepts):
            self.start_element(PROGRAM_ENTRY_POINT)
            addr = idaapi.get_entry(idaapi.get_entry_ordinal(i))
            self.write_address_attribute(ADDRESS, addr)
            self.close_tag()
        self.end_element(PROGRAM_ENTRY_POINTS)
        self.display_cpu_time(timer)


    def export_register_values(self):
        """
        Exports segment register value ranges.
        """
        # should use legacy function if IDA > 660
        if IDA_SDK_VERSION < 670:
            self.export_register_values_legacy()
            return
        first = idaapi.ph_get_regFirstSreg()
        last  = idaapi.ph_get_regLastSreg()+1
        has_segregareas = False
        for j in range(first, last):
            nsegregareas = idaapi.get_srareas_qty2(j)
            if nsegregareas != 0:
                has_segregareas = True
                break;
        if has_segregareas == False:
            return
        self.update_status(REGISTER_VALUES)
        timer = time.clock();
        self.start_element(REGISTER_VALUES, True)
        sr = idaapi.segreg_area_t()
        for j in range(first, last):
            nsegregareas = idaapi.get_srareas_qty2(j)
            if nsegregareas == 0:
                continue
            for i in range(nsegregareas):
                success = idaapi.getn_srarea2(sr, j, i)
                if success == False:
                    continue
                value = sr.val
                if value == idaapi.BADSEL:
                    continue
                regname = idaapi.ph.regnames[j]
                if regname == None:
                    continue
                if regname.lower() == "cs":
                    continue
                if (idaapi.ph.id == idaapi.PLFM_TMS and
                    regname.lower() == "ds"):
                    continue
                self.start_element(REGISTER_VALUE_RANGE)
                self.write_attribute(REGISTER, idaapi.ph.regnames[j])
                self.write_numeric_attribute(VALUE, value)
                self.write_address_attribute(START_ADDRESS, sr.startEA)
                length = (sr.endEA - sr.startEA) * self.cbsize
                self.write_numeric_attribute(LENGTH, length)
                self.close_tag()
        self.end_element(REGISTER_VALUES)
        self.display_cpu_time(timer)


    def export_register_values_legacy(self):
        """
        Exports segment register value ranges.
        """
        # should use export_register_values() if IDA > 660
        if IDA_SDK_VERSION > 660:
            return
        if IDA_SDK_VERSION < 650:
            n = 0
            srarea = idaapi.getnSRarea(n)
            while type(srarea) == idaapi.segreg_t:
                n += 1
                srarea = idaapi.getnSRarea(n)
            nsegregareas = n
        else:
            nsegregareas = idaapi.get_srareas_qty()
        if (nsegregareas == 0):
            return
        self.update_status(REGISTER_VALUES)
        timer = time.clock();
        self.start_element(REGISTER_VALUES, True)
        for i in range(nsegregareas):
            if IDA_SDK_VERSION < 650:
                sr = idaapi.getnSRarea(i)
            else:
                sr = idaapi.getn_srarea(i)
            first = idaapi.ph_get_regFirstSreg()
            last  = idaapi.ph_get_regLastSreg()+1
            for j in range(first, last):
                value = idaapi.getSR(sr.startEA, j)
                if value == idaapi.BADSEL:
                    continue
                regname = idaapi.ph.regnames[j]
                if regname == None:
                    continue
                if regname.lower() == "cs":
                    continue
                if (idaapi.ph.id == idaapi.PLFM_TMS and
                    regname.lower() == "ds"):
                    continue
                self.start_element(REGISTER_VALUE_RANGE)
                self.write_attribute(REGISTER, idaapi.ph.regnames[j])
                self.write_numeric_attribute(VALUE, value)
                self.write_address_attribute(START_ADDRESS, sr.startEA)
                length = (sr.endEA - sr.startEA) * self.cbsize
                self.write_numeric_attribute(LENGTH, length)
                self.close_tag()
        self.end_element(REGISTER_VALUES)
        self.display_cpu_time(timer)


    def export_regular_cmt(self, cmt):
        """
        Exports the regular comment for an item.
        
        Args:
            cmt: String containing the regular comment.
        """
        self.write_comment_element(REGULAR_CMT, cmt)
    

    def export_repeatable_cmt(self, cmt):
        """
        Exports the repeatable comment for an item.
        
        Args:
            cmt: String containing the repeatable comment.
        """
        self.write_comment_element(REPEATABLE_CMT, cmt)
    

    def export_stack_frame(self, function):
        """
        Export information about a function stack frame including
        variables allocated on the stack.
        
        Args:
            function: IDA function instance
        """
        sframe = idaapi.get_struc(function.frame)
        if sframe == None or sframe.memqty <= 0:
            return
        self.start_element(STACK_FRAME)
        self.write_numeric_attribute(LOCAL_VAR_SIZE, function.frsize)
        self.write_numeric_attribute(REGISTER_SAVE_SIZE, function.frregs)
        retsize = idaapi.get_frame_retsize(function)
        self.write_numeric_attribute(RETURN_ADDR_SIZE, retsize)
        self.write_numeric_attribute(BYTES_PURGED, function.argsize)
        has_stack_vars = self.check_stack_frame(sframe)
        self.close_tag(has_stack_vars)
        if has_stack_vars == True:
            self.export_stack_vars(function, sframe)
            self.end_element(STACK_FRAME)

    def export_stack_reference(self, addr):
        """
        Exports references to stack variables at the address.
        
        Args:
            addr: Integer containing instruction address.
        """
        f = idaapi.getFlags(addr)
        for op in range(idaapi.UA_MAXOP):
            if idaapi.isCode(f) == True and idaapi.isStkvar(f, op) == True:
                idaapi.decode_insn(addr)
                opnd = idaapi.cmd.Operands[op]
                optype = idaapi.op_t_get_type(opnd)
                if optype == idaapi.o_void:
                    continue
                SV = idaapi.get_stkvar(opnd, idaapi.op_t_get_addr(opnd))
                if SV == None:
                    continue
                (sv, actval) = SV
                function = idaapi.get_func(addr)
                self.start_element(STACK_REFERENCE)
                self.write_address_attribute(ADDRESS, addr)
                self.write_numeric_attribute(OPERAND_INDEX, op, 10)
                offset = opnd.addr
                spoff = offset - function.frregs
                if offset > 0x7FFFFFFF:
                    offset -= 0x100000000
                if spoff > 0x7FFFFFFF:
                    spoff  -= 0x100000000
                self.write_numeric_attribute(STACK_PTR_OFFSET,
                                             spoff,
                                             16, True)
                if (function.flags & FUNC_FRAME) != 0:
                    self.write_numeric_attribute(FRAME_PTR_OFFSET,
                                                offset,
                                                16, True)
                self.close_tag()

    def export_stack_vars(self, function, sframe):
        """
        Exports the stack variables (parameters and locals) in a stack frame.
        
        Args:
            function: IDA function instance.
            sframe: IDA stack frame instance.
        """
        for i in range(sframe.memqty):
            member = sframe.get_member(i)
            if member == None:
                continue
            mname = idaapi.get_member_name(member.id)
            if mname == None or len(mname) < 0:
                continue
            if mname == " s" or mname == " r":
                continue
            spoff = member.soff - function.frsize - function.frregs
            froff = member.soff - function.frsize
            self.start_element(STACK_VAR)
            self.write_numeric_attribute(STACK_PTR_OFFSET, spoff, 16, True)
            if function.flags & idaapi.FUNC_FRAME != 0:
                self.write_numeric_attribute(FRAME_PTR_OFFSET, froff, 16, True)
            pre = mname[0:4]
            if pre != "var_" and pre != "arg_":
                self.write_attribute(NAME, mname)
            f = member.flag
            size = idaapi.get_member_size(member)
            mtype = self.get_member_type(member)
            msize = size
            if idaapi.isStruct(f) == True:
                msize = idaapi.get_struc_size(idaapi.get_struc_id(mtype))
            elif idaapi.isASCII(f) == False:
                mtibuf = idaapi.opinfo_t()
                mti = idaapi.retrieve_member_info(member, mtibuf)
                if IDA_SDK_VERSION < 640:
                    msize = idaapi.get_type_size0(None, mtype)
                    if msize == None or msize == 0:
                        msize = idaapi.get_member_size(member)
                else:
                    msize = idaapi.get_data_type_size(f, mtibuf)
            if size < msize: size = msize
            if (idaapi.isASCII(f) == False and idaapi.isAlign(f) == False
                and size != msize):
                mtype = "%s[%d]" % (mtype, size/msize)
            self.write_attribute(DATATYPE, mtype)
            self.write_numeric_attribute(SIZE, size*self.cbsize)
            regcmt = idaapi.get_member_cmt(member.id, False)
            rptcmt = idaapi.get_member_cmt(member.id, True)
            if regcmt != None:
                regcmt  = idaapi.tag_remove(regcmt + " ", 0)
            if rptcmt != None:
                rptrcmt = idaapi.tag_remove(rptcmt + " ", 0)
            has_regcmt = regcmt != None and len(regcmt) > 0
            has_rptcmt = rptcmt != None and len(rptcmt) > 0
            has_content = has_regcmt or has_rptcmt
            self.close_tag(has_content)
            if has_content == True:
                if has_regcmt == True:
                    self.export_regular_cmt(regcmt)
                if has_rptcmt == True:
                    self.export_repeatable_cmt(rptcmt)
                self.end_element(STACK_VAR)


    def export_structures(self):
        """
        Exports information about all structures and unions.
        """
        structs = idautils.Structs()
        for struct in structs:
            (sid, idx, sname) = struct
            s = idaapi.get_struc(idx)
            stype = STRUCTURE
            if s.is_union() == True:
                stype = UNION
            self.start_element(stype)
            self.write_attribute(NAME, sname)
            size = idaapi.get_struc_size(idx)*self.cbsize
            self.write_numeric_attribute(SIZE, size)
            if s.is_varstr() == True:
                self.write_attribute(VARIABLE_LENGTH, "y")
            regcmt = idaapi.get_struc_cmt(idx, False)
            rptcmt = idaapi.get_struc_cmt(idx, True)
            has_contents = regcmt != None or rptcmt != None or s.memqty > 0
            self.close_tag(has_contents)
            if (has_contents):
                if regcmt != None:
                    self.export_regular_cmt(regcmt)
                if rptcmt != None:
                    self.export_repeatable_cmt(rptcmt)
                if s.memqty > 0:
                    self.export_members(s)
                self.end_element(stype)
    

    def export_symbol(self, addr, name, stype=""):
        """
        Exports name for an address as a SYMBOL element. If the name is a
        demangled name, add the mangled name as the MANGLED attribute.
        
        Args:
            addr: Integer representing the symbol address.
            name: String containing the symbol name.
            stype: String indicating symbol type (global or local)
        """
        self.start_element(SYMBOL)
        self.write_address_attribute(ADDRESS, addr)
        self.write_attribute(NAME, name)
        self.write_attribute(TYPE, stype)
        mangled = idaapi.get_name(BADADDR, addr)
        if name != None and mangled != name:
            self.write_attribute("MANGLED", mangled)
        self.close_tag()
        

    def export_symbol_table(self):
        """
        Exports user-defined and non-default names as SYMBOL elements.
        """
        addr = self.min_ea
        if idaapi.has_any_name(idaapi.getFlags(addr)) == False:
            addr = idaapi.nextthat(addr, self.max_ea, idaapi.has_any_name)
        if addr == BADADDR:
            return
        self.update_status(SYMBOL_TABLE)
        self.start_element(SYMBOL_TABLE, True)
        timer = time.clock()
        while addr != BADADDR:
            # only export meaningful names (user and auto)
            f = idaapi.getFlags(addr)
            if (idaapi.has_user_name(f) == True or
                idaapi.has_auto_name(f) == True):
                # check for global name
                name = self.get_symbol_name(addr)
                if name != None and len(name) > 0:
                    self.export_symbol(addr, name)
                # check for local name
                if ((IDA_SDK_VERSION > 620 and
                    idaapi.has_lname(addr)) or
                    idaapi.get_aflags(addr) & idaapi.AFL_LNAME):
                    if IDA_SDK_VERSION < 680:
                        name = idaapi.get_name(addr, addr)
                    else:
                        name = idaapi.get_ea_name(addr, idaapi.GN_LOCAL)
                    if name != None and len(name) > 0:
                        self.export_symbol(addr, name, 'local')
            # get next address with any name
            addr = idaapi.nextthat(addr, self.max_ea,
                                   idaapi.has_any_name)
        self.end_element(SYMBOL_TABLE)
        self.display_cpu_time(timer)
        

    def export_typeinfo_cmt(self, cmt):
        """
        Exports comment containing type information for data and functions.
        
        Args:
            cmt: String containing type info.
        """
        # older versions of IDAPython returned a '\n' at end of cmt
        if cmt[-1] == '\n':
            cmt = cmt[0:-1]
        self.write_comment_element(TYPEINFO_CMT, cmt)
        

    def export_user_memory_reference(self, addr):
        """
        Exports a user-specified memory reference at the address.
        
        Args:
            addr: Integer representing the instruction address.
        """
        for xref in idautils.XrefsTo(addr, idaapi.XREF_FAR):
            if xref.user == 1:
                self.start_element(MEMORY_REFERENCE)
                self.write_address_attribute(ADDRESS, xref.frm)
                self.write_address_attribute(TO_ADDRESS, xref.to)
                self.write_attribute(USER_DEFINED, "y")
                self.close_tag()


    def find_overlay_memory(self):
        """
        Determines if any memory blocks (segments) are overlays.
        
        A segment is an overlay if it translates to the same logical
        address as another segment. This is rare, but may occur, for
        example when a processor has a small logical address space
        (i.e. a 16-bit address is limited to 64K) and multiple physical
        segments are mapped into the same logical segment.
        """
        self.overlay = dict()
        self.has_overlays = False;
        nsegs = idaapi.get_segm_qty()
        if nsegs == 0:
            return
        s = idaapi.getnseg(0)
        start = self.translate_address(s.startEA)
        self.overlay[start] = False
        for i in range(1, nsegs):
            s = idaapi.getnseg(i)
            space = self.get_space_name(s.startEA)
            saddr = self.translate_address(s.startEA)
            eaddr = self.translate_address(s.endEA-1)
            is_overlay = False
            for j in range(i):
                s2 = idaapi.getnseg(j)
                space2 = self.get_space_name(s2.startEA)
                if space == space2:
                    start = self.translate_address(s2.startEA)
                    end   = self.translate_address(s2.endEA-1)
                    if ((saddr >= start and saddr <= end) or 
                        (eaddr >= start and eaddr <= end)):
                        is_overlay = True
                        self.has_overlays = True
                        break
            self.overlay[saddr] = is_overlay


    def get_address_string(self, addr):
        """
        Returns a string representing the address.
        
        The representation is typically a hex string of the address,
        but may include a segment or space name prefixe based on the
        processor or architecture.
        
        Args:
            addr: Integer representing a program address.
        """
        temp = "0x%X" % (addr - idaapi.get_segm_base(idaapi.getseg(addr)))
        space = self.get_space_name(addr)
        if space != None:
            temp = "%s:%04X" % (space,
                            addr - idaapi.get_segm_base(idaapi.getseg(addr)))
        else:
            if (idaapi.ph_get_id() == idaapi.PLFM_386 and
                idaapi.getseg(addr).bitness == 0):
                base = idaapi.get_segm_para(idaapi.getseg(addr))
                temp = "%04X:%04X" % (base, addr - (base << 4))
        if idaapi.ph_get_id() == idaapi.PLFM_C166:
            temp = "0x%X" % addr
        if self.has_overlays == True and self.is_overlay(addr) == True:
            oname = idaapi.get_segm_name(idaapi.getseg(addr))
            if len(oname) > 0:
                temp = oname + "::" + temp
        return temp


    def get_data_value(self, addr):
        """
        Returns the data item value at an address based on its size.
        
        Args:
            addr: Integer representing a program address.
        """
        size = idaapi.get_item_size(addr)*self.cbsize
        if size == 1:   return idaapi.get_byte(addr)
        if size == 2:   return idaapi.get_16bit(addr)
        if size == 4:   return idaapi.get_32bit(addr)
        if size == 8:   return idaapi.get_64bit(addr)
        return 0
    

    def get_datatype(self, addr):
        """
        Returns the datatype at an address.
        
        The type could be a basic type (byte, word, dword, etc.),
        a structure, an array, a pointer, or a string type.
        
        Args:
            addr: Integer representing a program address.
        """
        f = idaapi.getFlags(addr)
        t = self.get_type(f)
        if idaapi.isStruct(f) == True:
            opndbuf = idaapi.opinfo_t()
            opnd = idaapi.get_opinfo(addr, 0, f, opndbuf)
            return idaapi.get_struc_name(opnd.tid)
        if idaapi.isASCII(f) == True:
            str_type = idc.GetStringType(addr)
            if str_type == idaapi.ASCSTR_TERMCHR:   return "string"
            if str_type == idaapi.ASCSTR_PASCAL:    return "string1"
            if str_type == idaapi.ASCSTR_LEN2:      return "string2"
            if str_type == idaapi.ASCSTR_LEN4:      return "string4"
            if str_type == idaapi.ASCSTR_UNICODE:   return "unicode"
            if str_type == idaapi.ASCSTR_ULEN2:     return "unicode2"
            if str_type == idaapi.ASCSTR_ULEN4:     return "unicode4"
            return "string"
        if idaapi.isOff0(f) == True: return "pointer"
        return t


    def get_format(self, flags):
        """
        Returns the display format of a data item based on its flags.
        
        Args:
            flags: Integer representing IDA item flags
            
        Returns:
            String representing IDA display format.
        """
        if idaapi.isChar0(flags): return "char"
        radix = idaapi.getRadix(flags, 0)
        if radix == 2:  return "binary"
        if radix == 8:  return "octal"
        if radix == 10: return "decimal"
        return "hex" # default
    

    def get_member_type(self, m):
        """
        Returns the datatype of a structure member.
        
        Args:
            m: IDA member instance.
            
        Returns:
            String representing member datatype.
        """
        f = m.flag
        t = self.get_type(f)
        if idaapi.isOff0(f) == True:
            t = "pointer"
        if idaapi.isStruct(f) == False:
            return t
        s = idaapi.get_sptr(m)
        if (s == None):
            return t
        sname = idaapi.get_struc_name(s.id)
        if (sname == None):
            return t
        return sname


    def get_options(self):
        """
        Displays the options menu and retrieves the option settings. 
        """
        fmt =  "HELP\n"
        fmt += "XML plugin (Python)\n"
        fmt += "IDA SDK: "+ str(IDA_SDK_VERSION) + "\n"
        fmt += "\n"
        fmt += "The XML interface provides a dump of the IDA-Pro database as "
        fmt += "a XML \"PROGRAM\" document. The XML PROGRAM document contains "
        fmt += "information from the idb file in a readable text format, and "
        fmt += "can be viewed with a text editor or web browser.\n\n"
        fmt += "ENDHELP\n"
        fmt += "Export as XML PROGRAM document...."
        fmt += "\n <##Options##Memory Sections:{MemorySections}>"
        fmt += "\n <Memory Content:{MemoryContent}>"
        fmt += "\n <Segment Register Value Ranges:{RegisterValues}>"
        fmt += "\n <Data Types:{DataTypes}>"
        fmt += "\n <Code Blocks:{CodeBlocks}>"
        fmt += "\n <Data Definitions:{DataDefinitions}>"
        fmt += "\n <Comments:{Comments}>"
        fmt += "\n <Entry Points:{EntryPoints}>"
        fmt += "\n <Symbols:{Symbols}>"
        fmt += "\n <Functions:{Functions}>"
        fmt += "\n <Memory References:{MemoryReferences}>"
        fmt += "\n <Stack References:{StackReferences}>"
        fmt += "\n <Manual Instructions/Operands:{Manual}>{cGroup1}>"
        fmt += "\n\n"
        
        Opts = { 'cGroup1': idaapi.Form.ChkGroupControl
            (
                (
                "MemorySections",
                "MemoryContent",
                "RegisterValues",
                "DataTypes",
                "CodeBlocks",
                "DataDefinitions",
                "Comments",
                "EntryPoints",
                "Symbols",
                "Functions",
                "MemoryReferences",
                "StackReferences",
                "Manual"
                )
            )
        }
        
        self.options = idaapi.Form(fmt, Opts)
        self.options.Compile()
        
        self.options.MemorySections.checked   = True
        self.options.MemoryContent.checked    = True
        self.options.DataTypes.checked        = True
        self.options.RegisterValues.checked   = True
        self.options.CodeBlocks.checked       = True
        self.options.DataDefinitions.checked  = True
        self.options.Symbols.checked          = True
        self.options.EntryPoints.checked      = True
        self.options.Functions.checked        = True
        self.options.Comments.checked         = True
        self.options.MemoryReferences.checked = True
        self.options.StackReferences.checked  = False
        self.options.Manual.checked           = True
    
        if (self.autorun == False):
            ok = self.options.Execute()
            if (ok == 0):
                raise Cancelled


    def get_space_name(self, addr):
        """
        Returns the memory space name associated with an address.
        
        Args:
            addr: Integer representing a program address.
            
        Returns:
            String containg the memory space name.
            None if single address space architecture.

        Used for Harvard architectures (Intel 8051 and TMS, add others
        as needed). 
        """
        pid = idaapi.ph_get_id()
        stype = idaapi.segtype(addr)
        if pid == idaapi.PLFM_8051:
            if stype == idaapi.SEG_CODE:
                return "CODE"
            else:
                if stype == idaapi.SEG_IMEM:
                    iaddr = addr - idaapi.get_segm_base(idaapi.getseg(addr))
                    if iaddr < 0x80:
                        return "INTMEM"
                    else:
                        return "SFR"
                else:
                    return "EXTMEM"
        if pid == idaapi.PLFM_TMS:
            if stype == idaapi.SEG_CODE:
                return "CODE"
            else:
                return "DATA"
        return None


    def get_symbol_name(self, ea):
        """
        Returns the symbol name for the address.
        
        Args:
            ea: Integer representing the symbol address.
            
        Returns:
            String containing the symbol name.
        
        The demangled name will be returned if it exists, otherwise the
        displayed name is returned. Spaces (' ') will be replaced with '_'.
        """
        flags = idaapi.getFlags(ea)
        name = idaapi.get_demangled_name(BADADDR, ea, DEMANGLED_FORM,
                                         self.inf.demnames, False)
        if name == None or len(name) == 0 or name == "`string'":
            name = idaapi.get_name(BADADDR, ea)
        if name != None:
            name = name.replace(" ","_")
        return name


    def get_type(self, flags):
        """
        Returns a datatype string based on the item flags.
        
        Args:
            flags: IDA item flags.
            
        Returns:
            String representing item datatype.
        """
        if (self.cbsize == 2):
            if idaapi.isByte(flags) == True: return "word"
            if idaapi.isWord(flags) == True: return "dword"
        if idaapi.isByte(flags)     == True: return "byte"
        if idaapi.isWord(flags)     == True: return "word"
        if idaapi.isDwrd(flags)     == True: return "dword"
        if idaapi.isQwrd(flags)     == True: return "qword"
        if idaapi.isOwrd(flags)     == True: return "oword"
        if idaapi.isTbyt(flags)     == True: return "tbyte"
        if idaapi.isFloat(flags)    == True: return "float"
        if idaapi.isDouble(flags)   == True: return "double"
        if idaapi.isPackReal(flags) == True: return "packed"
        if idaapi.isASCII(flags)    == True: return "ascii"
        if idaapi.isStruct(flags)   == True: return "structure"
        if idaapi.isAlign(flags)    == True: return "align"
        return "unknown"


    def is_imm_op(self, addr, op):
        """
        Returns true if instruction operand at address is an immediate value.
        
        Args:
            addr: Integer representing instruction address.
            op: Integer representing operand index (0-based).
            
        Returns:
            True if instruction operand at address is an immediate value.
            False otherwise.
        """
        idaapi.decode_insn(addr)
        if (idaapi.cmd.Operands[op].type == idaapi.o_imm):
            return True
        return False
        

    def is_overlay(self, addr):
        """
        Checks if memory block (segment) is an overlay.
        
        Args:
            addr: Integer representing a program address.
            
        Returns:
            True if memory block (segment) is an overlay.
        """
        if idaapi.ph_get_id() == idaapi.PLFM_C166:
            return False
        s = idaapi.getseg(addr)
        if s.startEA in self.overlay:
            return self.overlay[s.startEA]
        return False
    
    def is_signed_data(self, flags):
        return (flags & idaapi.FF_SIGN) != 0


    def open_file(self, filename, mode):
        """
        Opens filename to specified mode.
        
        Args:
            filename: String representing absolute filepath.
            mode: String representing mode for open.
            
        Returns
            File handle.
        
        Exceptions:
            Displays a warning and raises FileError exception
            if open fails.
        """
        try:
            f = open(filename, mode)
            return f
        except:
            fmt = "TITLE ERROR!\n"
            fmt += "ICON ERROR\n"
            fmt += "AUTOHIDE NONE\n"
            fmt += "HIDECANCEL\n"
            fmt += "Error opening file" + filename + "!\n"
            idaapi.warning(fmt)
            raise FileError


    def start_element(self, tag, close=False):
        """
        Outputs the start of a new element on a new indented line.
        
        Args:
            tag: String representing the element tag
            close: Boolean indicating if tag is should be closed.
        """
        if idaapi.wasBreak() == True:
            raise Cancelled
        self.write_to_xmlfile("\n" + ("    " * self.indent_level) + "<" + tag)
        if (close):
            self.close_tag(True)
        # Add a counter for this tag; increment counter if it already exists.
        if (tag in self.elements) == False:
            self.elements[tag] = len(self.elements)
            self.counters.append(1)
            self.tags.append(tag)
        else:
            self.counters[self.elements[tag]] += 1
        

    def translate_address(self, addr):
        """
        Returns the translated logical address.

        The logical address is adjusted for the segment base address.
        For 16-bit segmented memory, return the 20-bit address.
        
        Args:
            addr: Integer representing a program address.
            
        Returns:
            Integer representing the logical address.
        """
        if self.seg_addr == False:
            return addr - idaapi.get_segm_base(idaapi.getseg(addr))
        base = idaapi.get_segm_para(idaapi.getseg(addr))
        return (base << 16) + (addr - (base << 4))
    

    def update_status(self, tag):
        """
        Displays the processing status in the IDA window.
        
        Args:
            tag: String representing XML element tag
        """
        status = "Exporting <" + tag + ">"
        idaapi.msg('\n%-35s' % status)
        idaapi.hide_wait_box()
        idaapi.show_wait_box(status)


    def write_address_attribute(self, name, addr):
        """
        Outputs an address attribute for an element.
        
        Args:
            name: String representing attribute name.
            addr: Integer representing a program address.
        """
        self.write_attribute(name, self.get_address_string(addr))
    

    def write_attribute(self, name, value):
        """
        Outputs an attribute (name and value) for an element.
        
        Args:
            name: String representing attribute name.
            value: String representing attribute value.
        """
        if name == None or value == None:
            return
        if (len(name) == 0) or (len(value) == 0):
            return
        attr = " " + name + '="' + self.check_for_entities(value) + '"'
        self.write_to_xmlfile(attr)
        

    def write_comment_element(self, name, cmt):
        """
        Outputs the tag and text for a comment element.
        Comment elements can be REGULAR_CMT, REPEATABLE_CMT, or TYPEINFO_CMT.
        
        Args:
            name: String representing the comment element name.
            cmt: String containing the comment.
        """
        self.start_element(name, True)
        self.write_text(cmt)
        self.end_element(name, False)
        

    def write_numeric_attribute(self, name, value, base=16, signedhex=False):
        """
        Outputs a numeric value attribute (name and value) for an element.
        
        Args:
            name: String representing the attribute name.
            value: Integer representing the attribute value.
            base: Integer representing numeric base to use for value.
            signedhex: Boolean indicating if hex representation of
                value is signed.
        """
        if base == 10:
            temp = "%d" % value
        else:
            if signedhex == True and value < 0:
                temp = "-0x%X" % abs(value)
            else:
                temp = "0x%X" % value
        self.write_attribute(name, temp)


    def write_text(self, text):
        """
        Outputs the parsed character text for an element.
        The text is checked for special characters.
        
        Args:
            text: String representing the element text.
        """
        self.write_to_xmlfile(self.check_for_entities(text))
    

    def write_to_xmlfile(self, buf):
        """
        Writes the buffer to the XML file.
        
        Args:
            buf: String containg data to write to XML file.
        """
        self.xmlfile.write(buf)
        self.dbg(buf)
    

    def write_xml_declaration(self):
        """
        Writes the XML Declarations at the start of the XML file.
        """
        self.dbg("\n")
        xml_declaration  = "<?xml version=\"1.0\" standalone=\"yes\"?>"
        xml_declaration += "\n<?program_dtd version=\"1\"?>\n"
        self.write_to_xmlfile(xml_declaration)

# Global constants
# mangled name inhibit flags are not exposed in idaapi
# inhibit flags for symbol names
# DEMANGLE_FORM (MNG_SHORT_FORM | MNG_NOBASEDT | MNG_NOCALLC | MNG_NOCSVOL)
DEMANGLED_FORM = 0x0ea3ffe7
# inhibit flags for typeinfo cmts
# DEMANGLED_TYPEINFO (MNG_LONG_FORM)
DEMANGLED_TYPEINFO = 0x06400007

# Global XML string constants for elements and attributes
ADDRESS = 'ADDRESS'
ADDRESS_MODEL = 'ADDRESS_MODEL'
ADDRESS_RANGE = 'ADDRESS_RANGE'
BASE_ADDRESS = 'BASE_ADDRESS'
BIT_FIELD = 'BIT_FIELD'
BIT_MAPPED = 'BIT_MAPPED'
BIT_MASK = 'BIT_MASK'
BOOKMARK = 'BOOKMARK'
BOOKMARKS = 'BOOKMARKS'
BYTES = 'BYTES'
BYTES_PURGED = 'BYTES_PURGED'
CATEGORY = 'CATEGORY'
CODE = 'CODE'
CODE_BLOCK = 'CODE_BLOCK'
COMMENT = 'COMMENT'
COMMENTS = 'COMMENTS'
COMPILER = 'COMPILER'
DATA = 'DATA'
DATATYPE = 'DATATYPE'
DATATYPES = 'DATATYPES'
DATATYPE_NAMESPACE = 'DATATYPE_NAMESPACE'
DEFINED_DATA = 'DEFINED_DATA'
DESCRIPTION = 'DESCRIPTION'
DISPLAY_SETTINGS = 'DISPLAY_SETTINGS'
END = 'END'
ENDIAN = 'ENDIAN'
ENTRY_POINT = 'ENTRY_POINT'
ENUM = 'ENUM'
ENUM_ENTRY = 'ENUM_ENTRY'
EQUATE = 'EQUATE'
EQUATES = 'EQUATES'
EQUATE_GROUP = 'EQUATE_GROUP'
EQUATE_REFERENCE = 'EQUATE_REFERENCE'
EXE_FORMAT = 'EXE_FORMAT'
EXE_PATH = 'EXE_PATH'
EXT_LIBRARY = 'EXT_LIBRARY'
EXT_LIBRARY_REFERENCE = 'EXT_LIBRARY_REFERENCE'
EXT_LIBRARY_TABLE = 'EXT_LIBRARY_TABLE'
FAMILY = 'FAMILY'
FILE = 'FILE'
FILE_NAME = 'FILE_NAME'
FILE_OFFSET = 'FILE_OFFSET'
FOLDER = 'FOLDER'
FORMAT = 'FORMAT'
FRAGMENT = 'FRAGMENT'
FRAME_PTR_OFFSET = 'FRAME_PTR_OFFSET'
FUNCTION = 'FUNCTION'
FUNCTIONS = 'FUNCTIONS'
FUNCTION_DEF = 'FUNCTION_DEF'
IMAGE_BASE = 'IMAGE_BASE'
INPUT_MD5 = 'INPUT_MD5'
INFO_SOURCE = 'INFO_SOURCE'
LANGUAGE_PROVIDER = 'LANGUAGE_PROVIDER'
LENGTH = 'LENGTH'
LIB_ADDR = 'LIB_ADDR'
LIB_LABEL = 'LIB_LABEL'
LIB_ORDINAL = 'LIB_ORDINAL'
LIB_PROG_NAME = 'LIB_PROG_NAME'
LIBRARY_FUNCTION = 'LIBRARY_FUNCTION'
LOCAL_VAR_SIZE = 'LOCAL_VAR_SIZE'
MANGLED = 'MANGLED'
MANUAL_INSTRUCTION = 'MANUAL_INSTRUCTION'
MANUAL_OPERAND = 'MANUAL_OPERAND'
MARKUP = 'MARKUP'
MEMBER = 'MEMBER'
MEMORY_CONTENTS = 'MEMORY_CONTENTS'
MEMORY_MAP = 'MEMORY_MAP'
MEMORY_REFERENCE = 'MEMORY_REFERENCE'
MEMORY_SECTION = 'MEMORY_SECTION'
NAME = 'NAME'
NAMESPACE = 'NAMESPACE'
OFFSET = 'OFFSET'
OPERAND_INDEX = 'OPERAND_INDEX'
PARAM_OFFSET = 'PARAM_OFFSET'
PATH = 'PATH'
PERMISSIONS = 'PERMISSIONS'
PRIMARY = 'PRIMARY'
PROCESSOR = 'PROCESSOR'
PROGRAM = 'PROGRAM'
PROGRAM_ENTRY_POINT = 'PROGRAM_ENTRY_POINT'
PROGRAM_ENTRY_POINTS = 'PROGRAM_ENTRY_POINTS'
PROGRAM_TREES = 'PROGRAM_TREES'
PROPERTIES = 'PROPERTIES'
PROPERTY = 'PROPERTY'
REGISTER = 'REGISTER'
REGISTER_SAVE_SIZE = 'REGISTER_SAVE_SIZE'
REGISTER_VALUES = 'REGISTER_VALUES'
REGISTER_VALUE_RANGE = 'REGISTER_VALUE_RANGE'
REGISTER_VAR = 'REGISTER_VAR'
REGULAR_CMT = 'REGULAR_CMT'
RELOCATION = 'RELOCATION'
RELOCATION_TABLE = 'RELOCATION_TABLE'
REPEATABLE_CMT = 'REPEATABLE_CMT'
RETURN_ADDR_SIZE = 'RETURN_ADDR_SIZE'
RETURN_TYPE = 'RETURN_TYPE'
SHOW_TERMINATOR = 'SHOW_TERMINATOR'
SIGNED = 'SIGNED'
SIZE = 'SIZE'
SOURCE_ADDRESS = 'SOURCE_ADDRESS'
SOURCE_TYPE = 'SOURCE_TYPE'
STACK_FRAME = 'STACK_FRAME'
STACK_PTR_OFFSET = 'STACK_PTR_OFFSET'
STACK_REFERENCE = 'STACK_REFERENCE'
STACK_VAR = 'STACK_VAR'
START = 'START'
START_ADDR = 'START_ADDR'
START_ADDRESS = 'START_ADDRESS'
STRUCTURE = 'STRUCTURE'
SYMBOL = 'SYMBOL'
SYMBOL_TABLE = 'SYMBOL_TABLE'
TIMESTAMP = 'TIMESTAMP'
TOOL = 'TOOL'
TO_ADDRESS = 'TO_ADDRESS'
TREE = 'TREE'
TYPE = 'TYPE'
TYPEINFO_CMT = 'TYPEINFO_CMT'
TYPE_DEF = 'TYPE_DEF'
UNION = 'UNION'
USER = 'USER'
USER_DEFINED = 'USER_DEFINED'
VALUE = 'VALUE'
VARIABLE_LENGTH = 'VARIABLE_LENGTH'
ZERO_PAD = 'ZERO_PAD'


repository: yashchaurasia667/Binary-Exploitation
file: Tools/ghidra_11.3.1_PUBLIC/Extensions/IDAPro/Python/6xx/plugins/xmlexp.py
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2023-11-11 13:49:18+00:00
pushed_at: 2025-04-26 07:50:25+00:00
url:
https://github.com/yashchaurasia667/Binary-Exploitation/blob/bf9e64a8a9d630289d3fae68cd2f7cd6f88648eb/Tools/ghidra_11.3.1_PUBLIC/Extensions/IDAPro/Python/6xx/plugins/xmlexp.py


repository: k3rn3Lp4n1cK/ctf-live-build-config
description: Custom Kali Live Builder for CTF's & OSCP
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2023-01-28 21:06:18+00:00
pushed_at: 2023-05-18 00:05:00+00:00
file:
kali-config/common/includes.chroot/opt/ghidra_10.2.2_PUBLIC/Extensions/IDAPro/Python/6xx/plugins/xmlexp.py

url:
https://github.com/k3rn3Lp4n1cK/ctf-live-build-config/blob/6ca05257d5cdecfa6dc05ab1a8258506864f6edd/kali-config/common/includes.chroot/opt/ghidra_10.2.2_PUBLIC/Extensions/IDAPro/Python/6xx/plugins/xmlexp.py


repository: mahmoudimus/.idapro
file: plugins/sigmaker.py
comment: f"{PLUGIN_NAME} v{PLUGIN_VERSION} for IDA Pro by {PLUGIN_AUTHOR}
created_at: 2025-02-16 21:33:20+00:00
pushed_at: 2025-04-09 18:15:30+00:00
url:
https://github.com/mahmoudimus/.idapro/blob/e0bf0d58fb6de4eb6af0b87ffbd869d8c7deef08/plugins/sigmaker.py

content:
"""
sigmaker.py - IDA Python port of Signature Maker (from A200K)
https://github.com/mahmoudimus/ida-pysigmaker

by @mahmoudimus (Mahmoud Abdelkader)
"""

import ctypes
import enum
import os
import platform
import re
import sys
import traceback

import ida_bytes
import ida_ida
import ida_idaapi
import ida_idp
import ida_kernwin
import ida_xref
import idaapi
import idc


def _IsProcessorFeaturePresent(feature: int) -> bool:
    if platform.system() != "Windows":
        return False

    try:
        is_present = ctypes.windll.kernel32.IsProcessorFeaturePresent(feature)
    except Exception:
        is_present = False
    return is_present


def _IsAVX2Available() -> bool:
    # Check for AVX2 feature to enable QIS signature scanning.
    PF_AVX2_INSTRUCTIONS_AVAILABLE = 10
    return _IsProcessorFeaturePresent(PF_AVX2_INSTRUCTIONS_AVAILABLE)


# Globals
__author__ = "mahmoudimus"
__version__ = "1.1.2"

PLUGIN_NAME = "Signature Maker (py)"
PLUGIN_VERSION = __version__
PLUGIN_AUTHOR = __author__
USE_QIS_SIGNATURE = False  # _IsAVX2Available()  # TODO: add this later
PRINT_TOP_X = 5
MAX_SINGLE_SIGNATURE_LENGTH = 1000
MAX_XREF_SIGNATURE_LENGTH = 250
FILE_BUFFER = None
PROCESSOR_ARCH = ida_idp.ph_get_id()  # Check what processor we have
WILDCARD_OPTIMIZED_INSTRUCTION = True
WildcardableOperandTypeBitmask = 0  # Will be set based on processor

# Helpers


def BIT(x):
    return 1 << x


class ProgressDialog:
    def __init__(self, message="Please wait...", hide_cancel=False):
        self._default_msg: str
        self.hide_cancel: bool
        self.configure(message, hide_cancel)

    def _message(self, message=None, hide_cancel=None):
        display_msg = self._default_msg if message is None else message
        hide_cancel = self.hide_cancel if hide_cancel is None else hide_cancel
        prefix = "HIDECANCEL\n" if hide_cancel else ""
        return prefix + display_msg

    def configure(self, message="Please wait...", hide_cancel=False):
        self._default_msg = message
        self.hide_cancel = hide_cancel
        return self

    __call__ = configure

    def __enter__(self):
        ida_kernwin.show_wait_box(self._message())
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        ida_kernwin.hide_wait_box()

    def replace_message(self, new_message, hide_cancel=False):
        msg = self._message(message=new_message, hide_cancel=hide_cancel)
        ida_kernwin.replace_wait_box(msg)

    def user_canceled(self):
        return ida_kernwin.user_cancelled()

    user_cancelled = user_canceled


class Clipboard:
    @staticmethod
    def _set_text_windows(text: str) -> bool:
        import ctypes.util
        from ctypes import wintypes as w

        GMEM_MOVEABLE = 0x0002
        GMEM_ZEROINIT = 0x0040
        CF_TEXT = 1

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

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

        EmptyClipboard = user32.EmptyClipboard
        EmptyClipboard.restype = w.BOOL

        GlobalAlloc = kernel32.GlobalAlloc
        GlobalAlloc.argtypes = (w.UINT, w.UINT)
        GlobalAlloc.restype = w.HGLOBAL

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

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

        SetClipboardData = user32.SetClipboardData
        SetClipboardData.argtypes = (w.UINT, w.HANDLE)
        SetClipboardData.restype = w.HANDLE

        CloseClipboard = user32.CloseClipboard
        CloseClipboard.restype = w.BOOL

        if not text:
            return False

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

        h_mem = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, len(text) + 1)
        if not h_mem:
            CloseClipboard()
            return False

        lp_str = GlobalLock(h_mem)
        if not lp_str:
            kernel32.GlobalFree(h_mem)
            CloseClipboard()
            return False

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

        if not SetClipboardData(CF_TEXT, h_mem):
            kernel32.GlobalFree(h_mem)
            CloseClipboard()
            return False

        CloseClipboard()
        return True

    @staticmethod
    def _set_text_macos(text: str) -> bool:
        import subprocess

        try:
            process = subprocess.Popen(
                ["pbcopy"], stdin=subprocess.PIPE, close_fds=True
            )
            process.communicate(input=text.encode("utf-8"))
            return process.returncode == 0
        except Exception as e:
            print(f"Error setting clipboard text on macOS: {e}")
            return False

    @staticmethod
    def _set_text_linux(text: str) -> bool:
        import subprocess

        try:
            process = subprocess.Popen(
                ["xclip", "-selection", "clipboard"],
                stdin=subprocess.PIPE,
                close_fds=True,
            )
            process.communicate(input=text.encode("utf-8"))
            return process.returncode == 0
        except Exception as e:
            print(f"Error setting clipboard text on Linux: {e}")
            return False

    @staticmethod
    def _set_text_pyqt5(text: str) -> bool:
        try:
            from PyQt5.QtWidgets import QApplication

            QApplication.clipboard().setText(text)
            return True
        except ImportError:
            return False
        except Exception as e:
            print(f"Error setting clipboard text on PyQt5: {e}")
            return False

    @classmethod
    def set_text(cls, text: str) -> bool:
        """
        Sets the clipboard text on the current operating system.
        Returns True on success, False on failure.
        """
        # Try to use PyQt5 to set the clipboard text, since
        # that is cross-platform and doesn't require any
        # external dependencies.
        success = cls._set_text_pyqt5(text)
        if success:
            return success

        # Otherwise, use the platform-specific method.
        if sys.platform.startswith("win"):
            return cls._set_text_windows(text)
        elif sys.platform == "darwin":
            return cls._set_text_macos(text)
        elif sys.platform.startswith("linux"):
            return cls._set_text_linux(text)
        else:
            print("Unsupported operating system")
            return False

    def __call__(self, text: str) -> bool:
        """
        Allow instances of Clipboard to be called like a function.
        """
        return self.set_text(text)


SetClipboardText = Clipboard.set_text


# -------------------------
# Signature structures and output functions
# -------------------------


class SignatureType(enum.Enum):
    IDA = 0
    x64Dbg = 1
    Signature_Mask = 2
    SignatureByteArray_Bitmask = 3


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


Signature = list  # list of SignatureByte


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


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


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


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


# -------------------------
# Utility functions (signature byte addition, trim, clipboard, regex)
# -------------------------


def AddByteToSignature(signature: Signature, address, wildcard: bool):
    b = ida_bytes.get_byte(address)
    signature.append(SignatureByte(b, wildcard))


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


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


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


# -------------------------
# Exception for errors
# -------------------------
class Unexpected(Exception):
    pass


class ConfigureOperandWildcardBitmaskForm(ida_kernwin.Form):
    """Interactive Form to configure wildcardable operands using checkboxes."""

    def __init__(self):
        F = ida_kernwin.Form
        # Define the form layout
        form_text = """BUTTON YES* OK
BUTTON CANCEL Cancel
Wildcardable Operands
{FormChangeCb}
Select operand types that should be wildcarded:

<General Register (al, ax, es, ds...):{opt1}>
<Direct Memory Reference (DATA) :{opt2}>
<Memory Ref [Base Reg + Index Reg] :{opt3}>
<Memory Ref [Base Reg + Index Reg + Displacement] :{opt4}>
<Immediate Value :{opt5}>
<Immediate Far Address (CODE) :{opt6}>
<Immediate Near Address (CODE) :{opt7}>"""
        registers = ["opt1", "opt2", "opt3", "opt4", "opt5", "opt6", "opt7"]

        # Processor-specific operand types
        PROCESSOR_ARCH = ida_idp.ph_get_id()
        if PROCESSOR_ARCH == ida_idp.PLFM_386:
            form_text += """
<Trace Register :{opt8}>
<Debug Register :{opt9}>
<Control Register :{opt10}>
<Floating Point Register :{opt11}>
<MMX Register :{opt12}>
<XMM Register :{opt13}>
<YMM Register :{opt14}>
<ZMM Register :{opt15}>
<Opmask Register :{opt16}>{cWildcardableOperands}>"""
            registers.extend(
                [
                    "opt8",
                    "opt9",
                    "opt10",
                    "opt11",
                    "opt12",
                    "opt13",
                    "opt14",
                    "opt15",
                    "opt16",
                ]
            )
        elif PROCESSOR_ARCH == ida_idp.PLFM_ARM:
            form_text += """
<(Unused) :{opt8}>
<Register list (for LDM/STM) :{opt9}>
<Coprocessor register list (for CDP) :{opt10}>
<Coprocessor register (for LDC/STC) :{opt11}>
<Floating point register list :{opt12}>
<Arbitrary text stored in the operand :{opt13}>
<ARM condition as an operand :{opt14}>{cWildcardableOperands}>"""
            registers.extend(
                ["opt8", "opt9", "opt10", "opt11", "opt12", "opt13", "opt14"]
            )
        elif PROCESSOR_ARCH == ida_idp.PLFM_PPC:
            form_text += """
<Special purpose register :{opt8}>
<Two FPRs :{opt9}>
<SH & MB & ME :{opt10}>
<crfield :{opt11}>
<crbit :{opt12}>
<Device control register :{opt13}>{cWildcardableOperands}>"""
            registers.extend(["opt8", "opt9", "opt10", "opt11", "opt12", "opt13"])
        else:
            form_text += """{cWildcardableOperands}>
"""
        # Shift by one because we skip o_void
        options = WildcardableOperandTypeBitmask >> 1
        # Define checkboxes
        controls = {
            "FormChangeCb": F.FormChangeCb(self.OnFormChange),
            "cWildcardableOperands": F.ChkGroupControl(
                tuple(registers),
                value=options,
            ),
        }
        # Initialize form
        super().__init__(form_text, controls)

    def OnFormChange(self, fid):
        """Handle form changes."""
        if fid == self.cWildcardableOperands.id:
            global WildcardableOperandTypeBitmask
            # print(f"cWildcardableOperands changed: {self.GetControlValue(self.cWildcardableOperands):06x}")
            # Re-shift by one because we skipped o_void
            WildcardableOperandTypeBitmask = (
                self.GetControlValue(self.cWildcardableOperands) << 1
            )
        return 1


class ConfigureOptionsForm(ida_kernwin.Form):
    """Interactive Form to configure XREF and signature generation options."""

    def __init__(self):
        F = ida_kernwin.Form

        # Define the form layout
        form_text = """BUTTON YES* OK
BUTTON CANCEL Cancel
Options

<#Print top X shortest signatures when generating xref signatures#Print top X XREF signatures     :{opt1}>
<#Stop after reaching X bytes when generating a single signature#Maximum single signature length :{opt2}>
<#Stop after reaching X bytes when generating xref signatures#Maximum xref signature length   :{opt3}>
"""

        # Define numerical input fields (corresponding to `u` in C++)
        self.controls = {
            "opt1": F.NumericInput(tp=F.FT_DEC),  # PRINT_TOP_X
            "opt2": F.NumericInput(tp=F.FT_DEC),  # MAX_SINGLE_SIGNATURE_LENGTH
            "opt3": F.NumericInput(tp=F.FT_DEC),  # MAX_XREF_SIGNATURE_LENGTH
        }

        # Initialize form
        super().__init__(form_text, self.controls)

    def ExecuteForm(self):
        """Execute the form and apply changes."""
        global PRINT_TOP_X, MAX_SINGLE_SIGNATURE_LENGTH, MAX_XREF_SIGNATURE_LENGTH

        # Pre-fill form values
        self.controls["opt1"].value = PRINT_TOP_X
        self.controls["opt2"].value = MAX_SINGLE_SIGNATURE_LENGTH
        self.controls["opt3"].value = MAX_XREF_SIGNATURE_LENGTH

        result = self.Execute()
        # Show form
        if result != 1:
            self.Free()
            return result

        # Apply the new values to the global variables
        PRINT_TOP_X = self.controls["opt1"].value
        MAX_SINGLE_SIGNATURE_LENGTH = self.controls["opt2"].value
        MAX_XREF_SIGNATURE_LENGTH = self.controls["opt3"].value
        self.Free()
        return result


class SignatureMakerForm(ida_kernwin.Form):
    def __init__(self):
        F = ida_kernwin.Form
        form_text = (
            f"""STARTITEM 0
BUTTON YES* OK
BUTTON CANCEL Cancel
Signature Maker v{PLUGIN_VERSION} {("(AVX2)" if USE_QIS_SIGNATURE else "")}"""
            + r"""
{FormChangeCb}
Select action:
<#Select an address, and create a code signature for it#Create unique signature for current code address:{rCreateUniqueSig}>
<#Select an address or variable, and create code signatures for its references. Will output the shortest 5 signatures#Find shortest XREF signature for current data or code address:{rFindXRefSig}>
<#Select 1+ instructions, and copy the bytes using the specified output format#Copy selected code:{rCopyCode}>
<#Paste any string containing your signature/mask and find matches#Search for a signature:{rSearchSignature}>{rAction}>

Output format:
<#Example - E8 ? ? ? ? 45 33 F6 66 44 89 34 33#IDA Signature:{rIDASig}>
<#Example - E8 ?? ?? ?? ?? 45 33 F6 66 44 89 34 33#x64Dbg Signature:{rx64DbgSig}>
<#Example - \\xE8\\x00\\x00\\x00\\x00\\x45\\x33\\xF6\\x66\\x44\\x89\\x34\\x33 x????xxxxxxxx#C Byte Array String Signature + String mask:{rByteArrayMaskSig}>
<#Example - 0xE8, 0x00, 0x00, 0x00, 0x00, 0x45, 0x33, 0xF6, 0x66, 0x44, 0x89, 0x34, 0x33 0b1111111100001#C Bytes Signature + Bitmask:{rRawBytesBitmaskSig}>{rOutputFormat}>

Quick Options:
<#Enable wildcarding for operands, to improve stability of created signatures#Wildcards for operands:{cWildcardOperands}>
<#Don't stop signature generation when reaching end of function#Continue when leaving function scope:{cContinueOutside}>
<#Wildcard the whole instruction when the operand (usually a register) is encoded into the operator#Wildcard optimized / combined instructions:{cWildcardOptimized}>{cGroupOptions}>

<Operand types...:{bOperandTypes}><Other options...:{bOtherOptions}>
"""
        )
        controls = {
            "cVersion": F.StringLabel(PLUGIN_VERSION),
            "FormChangeCb": F.FormChangeCb(self.OnFormChange),
            # Radio group for selecting the main action.
            "rAction": F.RadGroupControl(
                ("rCreateUniqueSig", "rFindXRefSig", "rCopyCode", "rSearchSignature")
            ),
            # Radio group for selecting output format.
            "rOutputFormat": F.RadGroupControl(
                ("rIDASig", "rx64DbgSig", "rByteArrayMaskSig", "rRawBytesBitmaskSig")
            ),
            # Checkboxes for quick options.
            "cGroupOptions": ida_kernwin.Form.ChkGroupControl(
                ("cWildcardOperands", "cContinueOutside", "cWildcardOptimized"),
                value=5,
            ),
            # Buttons for further configuration.
            "bOperandTypes": F.ButtonInput(self.ConfigureOperandWildcardBitmask),
            "bOtherOptions": F.ButtonInput(self.ConfigureOptions),
        }
        super().__init__(form_text, controls)

    def OnFormChange(self, fid):
        # (Optional: add code to respond to field changes.)
        # print(f"Form changed, fid: {fid}", self.rAction.id, self.rOutputFormat.id, self.cGroupOptions.id)
        # if fid == self.rAction.id:
        #     print(
        #         f"Action [{fid}] rAction changed: {self.GetControlValue(self.rAction):06x}"
        #     )
        # elif fid == self.rOutputFormat.id:
        #     print(
        #         f"Action [{fid}] rOutputFormat changed: {self.GetControlValue(self.rOutputFormat):06x}"
        #     )
        # elif fid == self.cGroupOptions.id:
        #     print(
        #         f"Action [{fid}] cGroupOptions changed: {self.GetControlValue(self.cGroupOptions):06x}"
        #     )
        # else:
        #     print(">>fid:%d" % fid)
        return 1

    def ConfigureOperandWildcardBitmask(self, code=0):
        form = ConfigureOperandWildcardBitmaskForm()
        form.Compile()
        ok = form.Execute()
        if not ok:
            return 0
        return 1

    def ConfigureOptions(self, code=0):
        """Launch the options configuration form."""
        form = ConfigureOptionsForm()
        form.Compile()
        return form.ExecuteForm()


# -------------------------
# The main plugin class
# -------------------------


def set_wildcardable_operand_type_bitmask():
    global WildcardableOperandTypeBitmask

    # Default wildcard setting depending on processor arch
    if PROCESSOR_ARCH == ida_idp.PLFM_386:
        o_ymmreg = idc.o_xmmreg + 1
        o_zmmreg = o_ymmreg + 1
        o_kreg = o_zmmreg + 1
        WildcardableOperandTypeBitmask = (
            BIT(idc.o_mem)
            | BIT(idc.o_phrase)
            | BIT(idc.o_displ)
            | BIT(idc.o_far)
            | BIT(idc.o_near)
            | BIT(idc.o_imm)
            | BIT(idc.o_trreg)
            | BIT(idc.o_dbreg)
            | BIT(idc.o_crreg)
            | BIT(idc.o_fpreg)
            | BIT(idc.o_mmxreg)
            | BIT(idc.o_xmmreg)
            | BIT(o_ymmreg)
            | BIT(o_zmmreg)
            | BIT(o_kreg)
        )
    elif PROCESSOR_ARCH == ida_idp.PLFM_ARM:
        WildcardableOperandTypeBitmask = (
            BIT(idc.o_mem)
            | BIT(idc.o_phrase)
            | BIT(idc.o_displ)
            | BIT(idc.o_far)
            | BIT(idc.o_near)
            | BIT(idc.o_imm)
        )
    elif PROCESSOR_ARCH == ida_idp.PLFM_MIPS:
        WildcardableOperandTypeBitmask = (
            BIT(idc.o_mem) | BIT(idc.o_far) | BIT(idc.o_near)
        )
    else:
        WildcardableOperandTypeBitmask = (
            BIT(idc.o_mem)
            | BIT(idc.o_phrase)
            | BIT(idc.o_displ)
            | BIT(idc.o_far)
            | BIT(idc.o_near)
            | BIT(idc.o_imm)
        )


class _ActionHandler(idaapi.action_handler_t):

    def __init__(self, action_function):
        super().__init__()
        self.action_function = action_function

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

    def update(self, ctx):
        if ctx.widget_type == idaapi.BWN_DISASM:
            return idaapi.AST_ENABLE_FOR_WIDGET
        return idaapi.AST_DISABLE_FOR_WIDGET


def is_disassembly_widget(widget, popup, ctx):
    return idaapi.get_widget_type(widget) == idaapi.BWN_DISASM


class _PopupHook(idaapi.UI_Hooks):

    def __init__(
        self, action_name, predicate=None, widget_populator=None, category=None
    ):
        super().__init__()
        self.action_name = action_name
        self.predicate = predicate or is_disassembly_widget
        self.widget_populator = widget_populator or self._default_populator
        self.category = category

    def term(self):
        idaapi.unregister_action(self.action_name)

    @staticmethod
    def _default_populator(instance, widget, popup_handle, ctx):
        if instance.predicate(widget, popup_handle, ctx):
            args = [widget, popup_handle, instance.action_name]
            if instance.category:
                args.append(f"{instance.category}/")
            idaapi.attach_action_to_popup(*args)

    def finish_populating_widget_popup(self, widget, popup_handle, ctx=None):
        return self.widget_populator(self, widget, popup_handle, ctx)


class PySigMaker(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_KEEP
    comment = f"{PLUGIN_NAME} v{PLUGIN_VERSION} for IDA Pro by {PLUGIN_AUTHOR}"
    help = "Select location in disassembly and press CTRL+ALT+S to open menu"
    wanted_name = PLUGIN_NAME
    wanted_hotkey = "Ctrl-Alt-S"
    IS_ARM = False

    ACTION_SHOW_SIGMAKER = "pysigmaker:show"

    def init(self):
        self.progress_dialog = ProgressDialog()
        self._hooks = self._init_hooks(_PopupHook(self.ACTION_SHOW_SIGMAKER))
        self._register_actions()
        return ida_idaapi.PLUGIN_KEEP

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

    def term(self):
        # unregister our actions & free their resources
        self._deregister_actions()
        # unhook our plugin hooks
        self._deinit_hooks(*self._hooks)

    def _init_hooks(self, *hooks):
        for hook in hooks:
            hook.hook()
        return hooks

    def _deinit_hooks(self, *hooks):
        for hook in hooks:
            hook.unhook()

    def _register_actions(self):
        # If the action is already registered, unregister it first.
        self._deregister_actions()
        # Describe the action using python3 copy
        idaapi.register_action(
            idaapi.action_desc_t(
                self.ACTION_SHOW_SIGMAKER,  # The action name.
                "SigMaker",  # The action text.
                _ActionHandler(self.run_plugin),  # The action handler.
                self.wanted_hotkey,  # Optional: action shortcut
                "Show the signature maker dialog.",  # Optional: tooltip
                154,  # magnifying glass icon
            )
        )

    def _deregister_actions(self):
        return idaapi.unregister_action(self.ACTION_SHOW_SIGMAKER)

    # -------------------------
    # Processor and operand handling
    # -------------------------
    def IsARM(self) -> bool:
        procname = ida_ida.inf_get_procname()
        return "ARM" in procname.upper()

    def GetOperandOffsetARM(self, instruction, operand_offset, operand_length) -> bool:
        # For ARM: assume operand is 3 bytes if size==4, 7 bytes if size==8.
        for op in instruction.ops:
            if op.type in {
                idaapi.o_mem,
                idaapi.o_far,
                idaapi.o_near,
                idaapi.o_phrase,
                idaapi.o_displ,
                idaapi.o_imm,
            }:
                operand_offset[0] = op.offb
                if instruction.size == 4:
                    operand_length[0] = 3
                elif instruction.size == 8:
                    operand_length[0] = 7
                else:
                    operand_length[0] = 0
                return True
        return False

    def GetOperand(
        self, instruction, operand_offset, operand_length, wildcard_optimized
    ) -> bool:
        # For non-ARM: iterate over operands and apply the bitmask and optimized–instruction check.
        if self.IS_ARM:
            return self.GetOperandOffsetARM(instruction, operand_offset, operand_length)
        for op in instruction.ops:
            if op.type == idaapi.o_void:
                continue
            # Only process operands that are marked in our bitmask.
            if (BIT(op.type) & WildcardableOperandTypeBitmask) == 0:
                continue
            isOptimizedInstr = op.offb == 0
            if isOptimizedInstr and not wildcard_optimized:
                continue
            operand_offset[0] = op.offb
            operand_length[0] = instruction.size - op.offb
            return True
        return False

    # -------------------------
    # QIS scanning support
    # -------------------------
    def ReadSegmentsToBuffer(self) -> bytes:
        buf = bytearray()
        seg = idaapi.get_first_seg()
        while seg:
            size = seg.end_ea - seg.start_ea
            data = ida_bytes.get_bytes(seg.start_ea, size)
            if data:
                buf.extend(data)
            seg = idaapi.get_next_seg(seg)
        return bytes(buf)

    def parse_signature(self, sig_str: str):
        # Convert a signature string (tokens separated by whitespace)
        # into a list of tuples (value, is_wildcard)
        tokens = sig_str.split()
        pattern = []
        for token in tokens:
            if "?" in token:
                pattern.append((0, True))
            else:
                try:
                    val = int(token, 16)
                except Exception:
                    val = 0
                pattern.append((val, False))
        return pattern

    def FindSignatureOccurencesQis(
        self, ida_signature: str, skip_more_than_one: bool = False
    ) -> list:
        global FILE_BUFFER
        if not FILE_BUFFER:
            with ProgressDialog("Please stand by, copying segments..."):
                FILE_BUFFER = self.ReadSegmentsToBuffer()
        # Convert to QIS signature string: simply double each '?'
        qis_signature = ida_signature.replace("?", "??")
        pattern = self.parse_signature(qis_signature)
        results = []
        base_ea = ida_ida.inf_get_min_ea()
        data = FILE_BUFFER
        pat_len = len(pattern)
        i = 0
        while i <= len(data) - pat_len:
            match = True
            for j, (val, is_wildcard) in enumerate(pattern):
                if not is_wildcard and data[i + j] != val:
                    match = False
                    break
            if match:
                results.append(base_ea + i)
                if skip_more_than_one and len(results) > 1:
                    break
            i += 1
        return results

    def FindSignatureOccurences(self, ida_signature: str) -> list:
        if USE_QIS_SIGNATURE:
            return self.FindSignatureOccurencesQis(ida_signature)
        # Otherwise use IDA’s built-in binary pattern search.
        binary_pattern = idaapi.compiled_binpat_vec_t()
        idaapi.parse_binpat_str(
            binary_pattern, ida_ida.inf_get_min_ea(), ida_signature, 16
        )
        results = []
        ea = ida_ida.inf_get_min_ea()
        _bin_search = getattr(ida_bytes, "bin_search", None)
        # See https://github.com/mahmoudimus/ida-pysigmaker/pull/2
        # In particular this discussion:
        # https://github.com/mahmoudimus/ida-pysigmaker/pull/2#discussion_r1991913976
        if not _bin_search:
            _bin_search = getattr(ida_bytes, "bin_search3")
        while True:
            occurence, _ = _bin_search(
                ea,
                ida_ida.inf_get_max_ea(),
                binary_pattern,
                ida_bytes.BIN_SEARCH_NOCASE | ida_bytes.BIN_SEARCH_FORWARD,
            )
            if occurence == idaapi.BADADDR:
                break
            results.append(occurence)
            ea = occurence + 1
        return results

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

    # -------------------------
    # Signature generation for unique EA and a range
    # -------------------------
    def GenerateUniqueSignatureForEA(
        self,
        ea,
        wildcard_operands,
        continue_outside_of_function,
        wildcard_optimized,
        max_signature_length=1000,
        ask_longer_signature=True,
    ):
        if ea == idaapi.BADADDR:
            raise Unexpected("Invalid address")
        if not idaapi.is_code(ida_bytes.get_flags(ea)):
            raise Unexpected("Cannot create code signature for data")
        signature = []
        sig_part_length = 0
        current_function = idaapi.get_func(ea)
        current_address = ea

        while True:
            if idaapi.user_cancelled():
                raise Unexpected("Aborted")
            instruction = idaapi.insn_t()
            current_instruction_length = idaapi.decode_insn(
                instruction, current_address
            )
            if current_instruction_length <= 0:
                if not signature:
                    raise Unexpected("Failed to decode first instruction")
                idc.msg(
                    f"Signature reached end of executable code @ {current_address:X}\n"
                )
                sig_str = BuildIDASignatureString(signature)
                idc.msg(f"NOT UNIQUE Signature for {ea:X}: {sig_str}\n")
                raise Unexpected("Signature not unique")
            if sig_part_length > max_signature_length:
                if ask_longer_signature:
                    result = idaapi.ask_yn(
                        idaapi.ASKBTN_YES,
                        f"Signature is already at {len(signature)} bytes. Continue?",
                    )
                    if result == 1:
                        sig_part_length = 0
                    elif result == 0:
                        sig_str = BuildIDASignatureString(signature)
                        idc.msg(f"NOT UNIQUE Signature for {ea:X}: {sig_str}\n")
                        raise Unexpected("Signature not unique")
                    else:
                        raise Unexpected("Aborted")
                else:
                    raise Unexpected("Signature exceeded maximum length")
            sig_part_length += current_instruction_length

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

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

            current_address += current_instruction_length

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

    def GenerateSignatureForEARange(
        self, ea_start, ea_end, wildcard_operands, wildcard_optimized
    ):
        if ea_start == idaapi.BADADDR or ea_end == idaapi.BADADDR:
            raise Unexpected("Invalid address")
        signature = []
        sig_part_length = 0
        if not idaapi.is_code(ida_bytes.get_flags(ea_start)):
            AddBytesToSignature(signature, ea_start, ea_end - ea_start, False)
            return signature

        current_address = ea_start
        while True:
            if idaapi.user_cancelled():
                raise Unexpected("Aborted")
            instruction = idaapi.insn_t()
            current_instruction_length = idaapi.decode_insn(
                instruction, current_address
            )
            if current_instruction_length <= 0:
                if not signature:
                    raise Unexpected("Failed to decode first instruction")
                idc.msg(
                    f"Signature reached end of executable code @ {current_address:X}\n"
                )
                if current_address < ea_end:
                    AddBytesToSignature(
                        signature, current_address, ea_end - current_address, False
                    )
                TrimSignature(signature)
                return signature
            sig_part_length += current_instruction_length

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

            if current_address >= ea_end:
                TrimSignature(signature)
                return signature
        raise Unexpected("Unknown")

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

    def FindXRefs(
        self,
        ea,
        wildcard_operands,
        continue_outside_of_function,
        wildcard_optimized,
        xref_signatures,
        max_signature_length,
    ):
        # Count code xrefs.
        xref_count = 0
        xb = ida_xref.xrefblk_t()
        if xb.first_to(ea, ida_xref.XREF_ALL):
            while True:
                if idaapi.is_code(ida_bytes.get_flags(xb.frm)):
                    xref_count += 1
                if not xb.next_to():
                    break
        # Process each xref.
        xb = ida_xref.xrefblk_t()
        if not xb.first_to(ea, ida_xref.XREF_ALL):
            return
        i = 0
        shortest_signature_length = max_signature_length + 1
        while True:
            if self.progress_dialog.user_cancelled():
                break
            if not idaapi.is_code(ida_bytes.get_flags(xb.frm)):
                if not xb.next_to():
                    break
                continue
            i += 1
            idaapi.replace_wait_box(
                f"Processing xref {i} of {xref_count} ({(i / xref_count) * 100.0:.1f}%)...\n\n"
                f"Suitable Signatures: {len(xref_signatures)}\n"
                f"Shortest Signature: {shortest_signature_length if shortest_signature_length <= max_signature_length else 0} Bytes"
            )
            try:
                sig = self.GenerateUniqueSignatureForEA(
                    xb.frm,
                    wildcard_operands,
                    continue_outside_of_function,
                    wildcard_optimized,
                    max_signature_length,
                    False,
                )
            except Exception:
                sig = None
            if sig:
                if len(sig) < shortest_signature_length:
                    shortest_signature_length = len(sig)
                xref_signatures.append((xb.frm, sig))
            if not xb.next_to():
                break
        xref_signatures.sort(key=lambda tup: len(tup[1]))

    def PrintXRefSignaturesForEA(self, ea, xref_signatures, sig_type, top_count):
        if not xref_signatures:
            idc.msg("No XREFs have been found for your address\n")
            return
        top_length = min(top_count, len(xref_signatures))
        idc.msg(
            f"Top {top_length} Signatures out of {len(xref_signatures)} xrefs for {ea:X}:\n"
        )
        for i in range(top_length):
            origin_address, signature = xref_signatures[i]
            sig_str = FormatSignature(signature, sig_type)
            idc.msg(f"XREF Signature #{i+1} @ {origin_address:X}: {sig_str}\n")
            if i == 0:
                SetClipboardText(sig_str)

    def PrintSelectedCode(
        self, start, end, sig_type, wildcard_operands, wildcard_optimized
    ):
        selection_size = end - start
        if selection_size <= 0:
            idc.msg("Invalid selection size\n")
            return
        try:
            signature = self.GenerateSignatureForEARange(
                start, end, wildcard_operands, wildcard_optimized
            )
        except Unexpected as e:
            idc.msg(f"Error: {str(e)}\n")
            return
        sig_str = FormatSignature(signature, sig_type)
        idc.msg(f"Code for {start:X}-{end:X}: {sig_str}\n")
        SetClipboardText(sig_str)

    def SearchSignatureString(self, input_str: str):
        converted_signature_string = ""
        string_mask = ""
        # Try to detect a string mask like "xx????xx?xx"
        m = re.search(r"x(?:x|\?)+", input_str)
        if m:
            string_mask = m.group(0)
        else:
            m = re.search(r"0b(?:[01]+)", input_str)
            if m:
                bits = m.group(0)[2:]
                reversed_bits = bits[::-1]
                string_mask = "".join("x" if b == "1" else "?" for b in reversed_bits)
        if string_mask:
            raw_byte_strings = []
            if GetRegexMatches(
                input_str,
                re.compile(r"\\x[0-9A-F]{2}", re.IGNORECASE),
                raw_byte_strings,
            ) and len(raw_byte_strings) == len(string_mask):
                converted_signature = []
                for i, m in enumerate(raw_byte_strings):
                    b = SignatureByte(int(m[2:], 16), string_mask[i] == "?")
                    converted_signature.append(b)
                converted_signature_string = BuildIDASignatureString(
                    converted_signature
                )
            elif GetRegexMatches(
                input_str,
                re.compile(r"(?:0x[0-9A-F]{2})+", re.IGNORECASE),
                raw_byte_strings,
            ) and len(raw_byte_strings) == len(string_mask):
                converted_signature = []
                for i, m in enumerate(raw_byte_strings):
                    b = SignatureByte(int(m[2:], 16), string_mask[i] == "?")
                    converted_signature.append(b)
                converted_signature_string = BuildIDASignatureString(
                    converted_signature
                )
            else:
                idc.msg(
                    f'Detected mask "{string_mask}" but failed to match corresponding bytes\n'
                )
        else:
            # Remove extraneous characters and normalize
            s = re.sub(r"[\)\(\[\]]+", "", input_str)
            s = re.sub(r"^\s+", "", s)
            s = re.sub(r"[? ]+$", "", s) + " "
            s = re.sub(r"\\?\\x", "", s)
            s = re.sub(r"\s+", " ", s)
            converted_signature_string = s

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

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

    # -------------------------
    # Main plugin UI and dispatch
    # -------------------------
    def run_plugin(self, ctx=None):
        # Determine processor type and set globals.
        self.IS_ARM = self.IsARM()

        set_wildcardable_operand_type_bitmask()

        # Show the main form.
        form = SignatureMakerForm()
        form.Compile()
        ok = form.Execute()
        if not ok:
            form.Free()
            return

        action = form.rAction.value
        output_format = form.rOutputFormat.value
        wildcard_operands = form.cGroupOptions.value & 1
        continue_outside_of_function = form.cGroupOptions.value & 2
        wildcard_optimized = form.cGroupOptions.value & 4

        # If the configuration buttons were pressed, they may have updated globals.
        form.Free()

        sig_type = SignatureType(output_format)

        try:
            if action == 0:
                # Create unique signature for current code address.
                ea = ida_kernwin.get_screen_ea()
                with self.progress_dialog("Generating signature..."):
                    sig = self.GenerateUniqueSignatureForEA(
                        ea,
                        wildcard_operands,
                        continue_outside_of_function,
                        wildcard_optimized,
                        MAX_SINGLE_SIGNATURE_LENGTH,
                    )
                    self.PrintSignatureForEA(sig, ea, sig_type)
            elif action == 1:
                # Find XREF signatures.
                ea = ida_kernwin.get_screen_ea()
                xref_signatures = []
                with self.progress_dialog(
                    "Finding references and generating signatures. This can take a while..."
                ):
                    self.FindXRefs(
                        ea,
                        wildcard_operands,
                        continue_outside_of_function,
                        wildcard_optimized,
                        xref_signatures,
                        MAX_XREF_SIGNATURE_LENGTH,
                    )
                    self.PrintXRefSignaturesForEA(
                        ea, xref_signatures, sig_type, PRINT_TOP_X
                    )
            elif action == 2:
                # Copy selected code.
                start, end = get_selected_addresses(idaapi.get_current_viewer())
                if start and end:
                    with self.progress_dialog("Please stand by..."):
                        self.PrintSelectedCode(
                            start, end, sig_type, wildcard_operands, wildcard_optimized
                        )
                else:
                    idc.msg("Select a range to copy the code!\n")
            elif action == 3:
                # Search for a signature.
                input_signature = idaapi.ask_str(
                    "", idaapi.HIST_SRCH, "Enter a signature"
                )
                if input_signature:
                    with self.progress_dialog("Searching..."):
                        self.SearchSignatureString(input_signature)
        except Unexpected as e:
            idc.msg(f"Error: {str(e)}\n")
        except Exception as e:
            print(e, os.linesep, traceback.format_exc())
            return


def get_selected_addresses(ctx):
    is_selected, start_ea, end_ea = idaapi.read_range_selection(ctx)
    if is_selected:
        return start_ea, end_ea

    # maybe it's the same line?
    p0, p1 = ida_kernwin.twinpos_t(), ida_kernwin.twinpos_t()
    ida_kernwin.read_selection(ctx, p0, p1)
    p0.place(ctx)
    p1.place(ctx)
    if p0.at and p1.at:
        start_ea = p0.at.toea()
        end_ea = p1.at.toea()
        if start_ea == end_ea:
            start_ea = idc.get_item_head(start_ea)
            end_ea = idc.get_item_end(start_ea)
            return start_ea, end_ea

    # if we are here, we haven't selected anything, so we use the current address
    start_ea = idaapi.get_screen_ea()
    try:
        end_ea = ida_kernwin.ask_addr(start_ea, "Enter end address for selection:")
    finally:
        # restore the cursor to the original address
        idc.jumpto(start_ea)

    if end_ea and end_ea <= start_ea:
        print(
            f"Error: End address 0x{end_ea:X} must be greater than start address 0x{start_ea:X}."
        )
        end_ea = None

    if end_ea is None:
        # if we canceled the dialog, let's assume the user wants
        # to select just the line they're on
        end_ea = idc.get_item_end(start_ea)
        print(f"No end address selected, using line end: 0x{end_ea:X}")

    return start_ea, end_ea


def PLUGIN_ENTRY():
    return PySigMaker()


