# plugins

repository: hugsy/stuff
file: ida_scripts/ida_rpyc_server.py
description: Unsorted, raw, ugly & probably poorly usable tools for reversing, exploit and pentest
created_at: 2014-05-01 00:10:53+00:00
pushed_at: 2024-08-28 20:11:06+00:00
url:
https://github.com/hugsy/stuff/blob/0530b13a65fc5c7028ebf2c9a9a42b64f7be1fc7/ida_scripts/ida_rpyc_server.py

content:
"""

> "Because IDA sucks"


Embed RPyc in IDA to expose IDA's API externally, by a background thread that runs the TCP server. Also in iPython this
provides autocomplete.

Props to https://github.com/vrtadmin/FIRST-plugin-ida/blob/master/first_plugin_ida/first.py#L87
for the workaround on the threading issue, for IDA Pro >= 7.2

Quick start
```
>>> import rpyc
>>> c = rpyc.connect("ida.rpyc.server", 18812)
#
# IDA namespace will be in `c.root`
#
>>> c.root.idaapi.get_root_filename()
'ntoskrnl.exe'
>>> hex( c.root.idc.here() )
0x140088194
>>> c.root.idaapi.jumpto( 0x1400881EE )
True
```

For more facility, you can alias it:
```
>>> idc = c.root.idc
```

Then, it becomes super readable
```
>>> idc.jumpto( idc.get_name_ea_simple("DriverEntry") )
True
>>> idc.set_cmt( idc.here(), "@hugsy was here", 1)
True
```

For generator objects, you now need to use the wrapper `c.root.iterate()`.

Example:
```
>>> idc = c.root.idc
>>> idautils = c.root.idautils
>>> for ea in c.root.iterate( idautils.Functions() ):
...    print( idc.get_func_name(ea) )
```

Blame HexRays for making their API more confusing at every release.

Ref:
- https://www.hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml

Demo:
- https://youtu.be/obX2GreSsFU

"""

import sys
import random
import threading

import rpyc
import idc
import idaapi
import idautils
import ida_bytes
import ida_funcs

# exported modules must be imported first
import sys
import os

PLUGIN_NAME = "RunRpycServer"
PLUGIN_HOTKEY = "Ctrl-Alt-K"
PLUGIN_VERSION = "0.2"
PLUGIN_AUTHOR = "@_hugsy_"

HOST, PORT = "0.0.0.0", 18812
DEBUG = False
MAX_RUNNING_INSTANCES = 10 if not DEBUG else 1

EXPOSED_MODULES = [idaapi, idc, idautils, os, sys]

def xlog(x):
    sys.stderr.write("{} - {}\n".format(threading.current_thread().name, x)) and sys.stderr.flush()

def err(msg):
    xlog("[!] {}".format(msg,))

def ok(msg):
    xlog("[+] {}".format(msg,))

def dbg(msg):
    if DEBUG:
        xlog("[*] {}".format(msg,))



class IdaWrapper:
    def __getattribute__(self, name):
        default = "IDoNotExistButNoReallyISeriouslyDoNotAndCannotExist"

        dbg("trying to get {}".format(name,))

        if name.startswith("exposed_"):
            name = name.replace("exposed_", "")
            dbg("changed to get {}".format(name,))

        for mod in EXPOSED_MODULES:
            val = getattr(mod, name, default)
            if val != default:
                break

        if val == default:
            raise AttributeError("unknown {}".format(name,))

        if hasattr(val, '__call__'):
            dbg("{} is callable".format(val,))
            def call(*args, **kwargs):
                holder = [None] # need a holder, because 'global' sucks

                def trampoline():
                    holder[0] = val(*args, **kwargs)
                    return 1

                idaapi.execute_sync(trampoline, idaapi.MFF_WRITE)
                return holder[0]
            return call
        else:
            return val



g_IdaWrapper     = IdaWrapper()


class IdaRpycService(rpyc.Service):
    ALIASES = ["ida", ]


    def on_connect(self, conn):
        ok("connect open: {}".format(conn,))
        for mod in EXPOSED_MODULES:
            setattr(self, "exposed_{}".format(mod.__name__), g_IdaWrapper)
        return


    def on_disconnect(self, conn):
        ok("connection closed: {}".format(conn,))
        return


    def exposed_eval(self, cmd):
        return eval(cmd)


    def exposed_exec(self, cmd):
        return exec(cmd)


    def exposed_iterate(self, iterator):
        default = "IDoNotExistButNoReallyISeriouslyDoNotAndCannotExist {}".format(random.randint(0, 65535),)
        holder = [default]
        def trampoline():
            try:
                holder[0] = next(iterator)
            except StopIteration:
                holder[0] = default
            return 1
        while True:
            idaapi.execute_sync(trampoline, idaapi.MFF_WRITE)
            if holder[0] == default:
                return
            yield holder[0]



g_IdaServer = IdaRpycService()



def start():
    global g_IdaServer
    srv = None
    port = -1
    for i in range(MAX_RUNNING_INSTANCES):
        port = PORT + i
        try:
            srv = rpyc.utils.server.OneShotServer(g_IdaServer, hostname=HOST, port=port) if DEBUG \
                else rpyc.utils.server.ThreadedServer(g_IdaServer, hostname=HOST, port=port)
            if not DEBUG and not srv:
                continue
            break
        except OSError:
            srv = None

    if not srv:
        err("failed to start server...")
        return

    ok(f"service listening on {HOST}:{port}...")
    srv.start()
    srv.close()
    ok("server closed")
    return


t = None

def main():
    global t
    if t is not None:
        err("thread is already running as {}".format(t))
        return

    t = threading.Thread(target=start)
    t.daemon = True
    t.start()


class dummy(idaapi.plugin_t):
    wanted_name = PLUGIN_NAME
    wanted_hotkey = ""
    flags = idaapi.PLUGIN_UNL
    comment = ""
    help = ""

    def init(self): return idaapi.PLUGIN_OK
    def run(self, arg): pass
    def term(self): pass


def PLUGIN_ENTRY():
    main()
    return dummy()


if __name__ == "__main__":
    PLUGIN_ENTRY()


repository: RomanRybachek/navigation_plugin
file: navigation_plugin.py
wanted_name: navigation_plugin
created_at: 2023-06-27 12:58:42+00:00
pushed_at: 2023-09-03 20:30:45+00:00
description:
This plugin helps to navigate among the large number of unexplored functions in the ida pro disassembler.

url:
https://github.com/RomanRybachek/navigation_plugin/blob/40972fa568de14f4c251fbc91d248bc8519adaf9/navigation_plugin.py

content:
import idaapi

from navigation_plugin.basic_analysis import *
from navigation_plugin.global_data_and_classes import *
from navigation_plugin.rename_rules import *

idaapi.require("navigation_plugin.basic_analysis")
idaapi.require("navigation_plugin.global_data_and_classes")
idaapi.require("navigation_plugin.rename_rules")

def print_all_funcinfo():
    for k, v in ALL_FUNC_INFO.items():
        print("func:", hex(k))
        for sub_r, sub_c in v.named_funcs.items():
            print("named:", hex(sub_r), sub_c)
        for loc in v.loc_funcs:
            print("loc:", hex(loc))
        for sub in v.sub_funcs:
            print("sub:", hex(sub))
        for imp in v.import_calls:
            print("imp:", hex(imp))
        if v.strings_num > 0:
            print("Str:", v.strings_num)
        if v.global_data > 0:
            print("data:", v.global_data)
        for sw in v.switches:
            print("switch:", hex(sw))
        print()

def advanced_analysis():
    # Что должен мочь advanced analysis?
    # - Находить циклы.
    # - Находить xrefы на unk_ данные.
    # - Находить вызовы через регистры: call rax:

    # хм... Чет как будто функционал не стоит реализации. 
    pass

def basic_analysis():

    global ALL_FUNC_INFO
    heads = idautils.Heads()

    check_deb = []
    for head in heads:                                                  # Go through all data and code marks.

        hdr_name    = ida_name.get_ea_name(head)
        flags       = ida_bytes.get_flags(head)

        if len(hdr_name) == 0:
            continue

        if ida_bytes.is_flow(flags) == True or hdr_name[:4] == "loc_":  # Handle local routines
            handle_loc_xref(head)
        elif hdr_name[:4] == "sub_" or hdr_name[:4] == "nav_":          # Handle custom functions
            handle_sub_xref(head)
        elif hdr_name[:4] == "jpt_":                                    # Handle switches
            handle_jpt_xrefs(head)
        elif ida_bytes.is_strlit(flags) == True:                        # Handle strings
            handle_strings_xrefs(head)
        elif head in ALL_IMP:                                           # Handle imports (but not import wrappers)
            handle_imp_xrefs(head)
        elif ida_bytes.is_data(flags) == True:                          # Handle data
            handle_data_xrefs(head)
        else:
            pass
    
    named_subs, import_wraps = obtain_named_subroutines_and_import_wrappers()
    for n in named_subs:
        handle_named_sub_xref(n)
    for i in import_wraps:                                              # Import wrappers are functions that only have
        handle_imp_xrefs(i)                                             # jmp instruction to address of imported function

    all_funcs = idautils.Functions()
    for f in all_funcs:
        if f not in ALL_FUNC_INFO:
            ALL_FUNC_INFO.update({f:FuncInfo()})

    setup_FuncInfo_objects()

def init():
    global ALL_IMP
    ALL_IMP = get_all_imports()

def fini():
    global ALL_IMP
    global ALL_FUNC_INFO

    ALL_FUNC_INFO.clear()
    ALL_IMP.clear()

def main():
    init()
    basic_analysis()
    run_rename_rules_for_all_fuctions()
    # run_rules_for_function_under_cursor()
    fini()

class navigation_plugin_class(idaapi.plugin_t):

    flags = idaapi.PLUGIN_UNL
    comment = "This plugin helps to navigate among the large number of unexplored functions in the ida pro disassembler."
    help = ""
    wanted_name = "navigation_plugin"
    #wanted_hotkey = "Ctrl+N"

    def init(self):
        idaapi.msg("Navigation_plugin init() called!\n")
        return idaapi.PLUGIN_OK
    def run(self, arg):
        start_time = time.time()
        main()
        print("--- %s seconds ---" % (time.time() - start_time))
        pass

    def term(self):
        idaapi.msg("term() called!\n")
        pass

def PLUGIN_ENTRY():
    return navigation_plugin_class()

comment:
This plugin helps to navigate among the large number of unexplored functions in the ida pro disassembler.


repository: joydo/d810
file: D810.py
url: https://github.com/joydo/d810/blob/87fe12ca9cfbea5744c30546308898bf7199c073/D810.py
wanted_name: D-810
comment: Interface to the D-810 plugin
created_at: 2021-04-15 02:59:38+00:00
pushed_at: 2023-12-20 10:32:57+00:00
content:
import os
import idaapi
import ida_hexrays
import ida_kernwin


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


D810_VERSION = "0.1"

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

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


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

        self.d810_config = D810Configuration()

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

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

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


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

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


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


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

        self.initialized = False


def PLUGIN_ENTRY():
    return D810Plugin()


repository: techbliss/Python_editor
file: 7.0/plugins/Python_editor.py
description: Better CodeEditor for Ida Pro.
wanted_name: Python Editor
comment: Run me
created_at: 2015-07-04 21:14:02+00:00
pushed_at: 2020-12-25 13:54:19+00:00
url:
https://github.com/techbliss/Python_editor/blob/4c2b67bd0b1a2b9b9403f5ae31cf8f13c247c981/7.0/plugins/Python_editor.py

content:
# Created by: Storm Shadow http://www.techbliss.org
import os
import ida_idaapi, ida_kernwin
import idc
from idc import *
from idaapi import *
import sys
sys.path.insert(0 , idaapi.idadir("plugins\\Code editor\\icons"))
import ico
from ico import *


PLUGIN_VERSION = "1.5"
IDAVERISONS    = "IDA PRO 7.0+"
AUTHORS        = "Storm Shadow"
DATE           = "2017"
TWITTER        = "Twitter @zadow28"

def banner():
    banner_options = (PLUGIN_VERSION, AUTHORS, DATE, TWITTER, IDAVERISONS)
    banner_titles = "Python Editor v%s - (c) %s - %s - %s - %s" % banner_options

# print plugin banner
    print("---[" + banner_titles + "]---\n")

banner()

# 1) Create the handler class
class MyEditorHandler(idaapi.action_handler_t):
    def __init__(self):
        idaapi.action_handler_t.__init__(self)

    # Run editor when invoked.
    def activate(self, ctx):
        g = globals()
        idahome = idaapi.idadir("plugins\\Code editor")
        IDAPython_ExecScript(idahome + "\\pyeditor.py", g)

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

class ripeye(idaapi.plugin_t):
    flags = idaapi.PLUGIN_FIX
    comment = "Run me"
    help = "Python Editor"
    wanted_name = "Python Editor"
    wanted_hotkey = "" #the tooltip horkey goes away when setting it here DONT DO it! and only is shown in File/Plugins menu


    def editor_menuaction(self):
        action_desc = idaapi.action_desc_t(
            'my:editoraction',  # The action name. This acts like an ID and must be unique
            'Python Editor!',  # The action text.
            MyEditorHandler(),  # The action handler.
            'Ctrl+H',  # Optional: the action shortcut DO IT  HERE!
            'Script editor',  # Optional: the action tooltip (available in menus/toolbar)
            idaapi.load_custom_icon(":/ico/python.png")  # hackish load action icon , if no custom icon use number from 1-150 from internal ida
        )

        # 3) Register the action
        idaapi.register_action(action_desc)

        idaapi.attach_action_to_menu(
            'File/Editor...',  # The relative path of where to add the action
            'my:editoraction',  # The action ID (see above)
            idaapi.SETMENU_APP)  # We want to append the action after the 'Manual instruction...

        form = idaapi.get_current_tform()
        idaapi.attach_action_to_popup(form, None, "my:editoraction", None)

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """
        #self._icon_id_file = idaapi.BADADDR
        # attempt plugin initialization
        try:
            self._install_plugin()

        # failed to initialize or integrate the plugin, log and skip loading
        except Exception as e:
            form = idaapi.get_current_tform()
            pass

        return PLUGIN_KEEP


    def _install_plugin(self):
        """
        Initialize & integrate the plugin into IDA.
        """
        self.editor_menuaction()
        self._init()

    def term(self):
        pass

    def run(self, arg = 0):
        #we need the calls again if we wanna load it via File/Plugins/editor
        idaapi.msg("Python Editor Loaded to menu \n use Alt+E hot key to quick load ")
        hackish = MyEditorHandler()
        hackish.activate(self)

def PLUGIN_ENTRY():
    return ripeye()


repository: sean2077/311SireCustomizedPackageDev
file: ida-scripts/311mem_tool.py
wanted_name: San11MemPlugin
comment: Import or export memory records (@san11pk).
created_at: 2024-07-05 14:15:05+00:00
pushed_at: 2024-07-12 13:43:21+00:00
description:
Learn to make scp (Sire Customized Package) for game "‎Sangokushi XI Power-up Kit"(san11pk) by KOEI. Inspired by https://github.com/sjn4048/311MemoryResearch.

url:
https://github.com/sean2077/311SireCustomizedPackageDev/blob/43afe4efe273cf3b6ab7833b9b6f5822bd331fd4/ida-scripts/311mem_tool.py

content:
"""
san11pk内存地址记录工具，支持：
- 内存地址汇总.md 中的记录导入到 IDA 中
- IDA 中的内存地址记录导出到 内存地址汇总.md
"""

import os
import re
from collections import defaultdict
from datetime import datetime

import idaapi
import idautils
import idc
from attrs import define, field
from prettytable import MARKDOWN, PrettyTable

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))

MEM_RECORDS_FILE = os.path.join(os.path.dirname(SCRIPT_DIR), "material", "内存地址汇总.md")

# 是否格式化 markdown 输出
FORMAT_MARKDOWN = False
# 是否去掉注释中的换行符
REMOVE_NEWLINE_IN_COMMENT = True

##########################################################################
###                               Utils                                ###
##########################################################################


def get_now_time() -> str:
    """获取当前时间，形如 2020-06-06 14:00:00"""
    now = datetime.now()
    return now.strftime("%Y-%m-%d %H:%M:%S")


def format_address(addr: int) -> str:
    """格式化地址"""
    return f"{addr:08x}"


def is_chinese(char):
    ranges = [
        ("\u4e00", "\u9fff"),  # CJK Unified Ideographs
        ("\u3400", "\u4dbf"),  # CJK Unified Ideographs Extension A
        ("\u20000", "\u2a6df"),  # CJK Unified Ideographs Extension B
        ("\u2a700", "\u2b73f"),  # CJK Unified Ideographs Extension C
        ("\u2b740", "\u2b81f"),  # CJK Unified Ideographs Extension D
        ("\u2b820", "\u2ceaf"),  # CJK Unified Ideographs Extension E
        ("\uf900", "\ufaff"),  # CJK Compatibility Ideographs
    ]
    return any(start <= char <= end for start, end in ranges)


def get_data_flags_size(dt_str: str) -> tuple[int, int]:
    if dt_str in ("byte",):
        return idaapi.byte_flag(), 1

    if dt_str in ("word", "short"):
        return idaapi.word_flag(), 2

    if dt_str in ("dword", "int"):
        return idaapi.dword_flag(), 4

    if dt_str in ("float",):
        return idaapi.float_flag(), 4

    if dt_str in ("pointer32", "pointer", "address"):
        return idaapi.FF_DWORD | idaapi.FF_1OFF | idaapi.FF_DATA, 4

    if dt_str in ("string",):
        return idaapi.strlit_flag(), 0

    if dt_str.startswith("struct_"):
        tid = idaapi.get_struc_id(dt_str)
        if tid == idaapi.BADADDR:
            return 0, -1
        return idaapi.stru_flag(), idaapi.get_struc_size(tid)

    return 0, -1  # 不支持类型


def is_auto_generated_name(name: str):
    auto_prefixes = ["sub_", "loc_", "j_", "def_"]
    return any(name.startswith(prefix) for prefix in auto_prefixes)


def reach_table_start(line: str) -> bool:
    """判断是否到达表格内容开始行"""
    return any(line.startswith(x) for x in ("| ---", "|--", "| :--", "|:--"))


def format_comment(comment: str) -> str:
    """注释"""
    comment = comment.strip()
    if REMOVE_NEWLINE_IN_COMMENT:
        comment = comment.replace("\\n", "").replace("\n", "")
    else:
        comment = comment.replace("\\n", "\n")
    return comment


##########################################################################
###                           内存地址记录相关                            ###
##########################################################################

RECORD_FIELDS = ("地址", "类型", "名称", "注释", "附加信息")


@define
class Record:
    address: int
    type: str
    name: str = ""
    comment: str = field(default="", converter=format_comment)
    info: dict = field(factory=dict)  # 附加信息, 形如 key1=value1,key2=value2

    @classmethod
    def from_table_row(cls, row: str):  # | 地址 | 类型 | 名称 | 注释 | 附加信息 |
        items = row.strip().strip("|").split("|")
        if len(items) != len(RECORD_FIELDS):
            raise ValueError(f"Invalid row: {row}")
        address, type_, name, comment, info = items
        address = int(address, 16)
        type_ = type_.strip()
        name = name.strip()
        comment = comment.strip()
        ret = cls(address, type_, name, comment)

        # 附加信息
        info = info.strip()
        if info:
            for item in info.split(";"):
                key, value = item.split("=")
                ret.info[key.strip()] = value.strip()

        return ret

    def check_opt(self, key: str) -> bool:
        """检查附加信息中是否存在 key, 且值为 1"""
        return key in self.info and self.info[key] == "1"

    def to_row(self) -> list[str]:
        return [
            format_address(self.address),
            self.type,
            self.name,
            self.comment.replace("\n", "\\n"),  # markdown 表格行中不支持换行
            ";".join([f"{k}={v}" for k, v in self.info.items()]),  # 附加信息
        ]


# 记录有问题的 record
_bad_records: dict[str, list[tuple[Record, str]]] = defaultdict(list)


def _add_bad_record(record: Record, reason_type: str, detail: str = ""):
    _bad_records[reason_type].append((record, detail))
    if detail:
        idaapi.msg(detail)


def _print_bad_records():
    for reason_type, records in _bad_records.items():
        idaapi.msg(f"Bad records ({reason_type}): {len(records)}\n")
        for record, detail in records:
            detail = detail.strip("\n")
            idaapi.msg(f"- {record.address:x} {record.name}: {detail}\n")


def collect_records(file_path: str = MEM_RECORDS_FILE) -> tuple[list[Record], dict[int, int]]:
    records = []
    record_addr_idx_map = {}
    name_set = set()

    with open(file_path, "r", encoding="utf-8") as f:
        reach_records = False
        for line in f:
            line = line.strip()
            if not line:
                if reach_records:  # 读取到记录后的空行，结束
                    break
                continue
            if not reach_records:
                if reach_table_start(line):
                    reach_records = True
                continue
            if reach_records:
                record = Record.from_table_row(line)
                # 名称冲突处理
                if record.name:
                    if record.name in name_set:
                        record.name += f"_{record.address:x}"
                        _add_bad_record(record, "name_conflict")
                    name_set.add(record.name)
                records.append(record)
                record_addr_idx_map[record.address] = len(records) - 1

    return records, record_addr_idx_map


def save_records(records: list[Record], dest_file: str = MEM_RECORDS_FILE):
    rows = []
    for record in records:
        rows.append(record.to_row())

    tb = PrettyTable()
    tb.set_style(MARKDOWN)
    tb.align = "l"
    tb.field_names = RECORD_FIELDS
    for row in rows:
        tb.add_row(row)

    # 先找出表格前的内容
    headers = []
    with open(dest_file, "r", encoding="utf-8") as f:
        for line in f:
            if reach_table_start(line):
                break
            if line.startswith("(最后更新时间："):
                line = f"(最后更新时间：{get_now_time()})\n"
            headers.append(line)
    headers = headers[:-1]  # 去掉表头

    with open(dest_file, "w", encoding="utf-8") as f:
        f.writelines(headers)
        if FORMAT_MARKDOWN:
            f.write(tb.get_string().replace("-|", " |"))  # 与 vscode markdown 插件格式化结果一致
        else:
            f.write((f"| {' | '.join(RECORD_FIELDS)} |\n"))
            f.write(f"| {' | '.join(['---'] * len(RECORD_FIELDS) )} |\n")
            for row in rows:
                f.write(f"| {' | '.join(row) } |\n")
        f.write("\n")


def sort_records(records: list[Record]):
    """对 records 进行排序"""
    # 默认按（类别, 地址）排序
    records.sort(key=lambda x: (x.type, x.address))


def _import_function(record: Record) -> bool:
    """导入类型为 "函数" 的记录"""
    func = idaapi.get_func(record.address)
    if not func or func.start_ea != record.address:
        return False

    idaapi.set_name(record.address, record.name)
    idaapi.set_func_cmt(record.address, record.comment, True)
    idaapi.msg(f"- Function at {record.address:x} name updated to {record.name}.\n")

    # 处理函数声明
    decl = idaapi.print_type(record.address, idaapi.PRTYPE_1LINE | idaapi.NTF_CHKSYNC)
    # 若未记录函数声明，则记录
    if "decl" not in record.info:
        if decl:
            record.info["decl"] = decl
    else:  # 否则，尝试更新函数签名
        tinfo = idaapi.tinfo_t()
        if idaapi.parse_decl(tinfo, None, record.info["decl"] + ";", idaapi.PT_SIL) is not None:
            if idaapi.apply_tinfo(record.address, tinfo, idaapi.TINFO_DEFINITE):
                idaapi.msg(f"- Function at {record.address:x} declaration updated to {record.info['decl']}.\n")
            else:
                _add_bad_record(
                    record,
                    "decl_update_failed",
                    f"Failed to update function at {record.address:x} declaration.\n",
                )
        else:
            # 无法解析函数声明，则更新函数签名为 IDA 中的签名
            _add_bad_record(
                record,
                "decl_parse_failed",
                f"Failed to parse function at {record.address:x} declaration: {record.info['decl']}, record declaration in IDA.\n",
            )
            if decl:
                record.info["decl"] = decl

    return True


def _import_parameter(record: Record) -> bool:
    """导入类型为 "参数" 的记录"""
    param_type = record.info.get("type", None)
    if not param_type:
        idaapi.msg(f"- Parameter at {record.address:x} has no type info. Skipped.\n")
        return False

    dt_flag, dt_sz = get_data_flags_size(param_type)
    if dt_sz == -1:
        _add_bad_record(record, "unsupported_data_type", f"Unsupported data type: {param_type}. Skipped.\n")
        return False

    if param_type.startswith("struct_"):
        tid = idaapi.get_struc_id(param_type)
        if not idaapi.create_struct(record.address, dt_sz, tid):
            _add_bad_record(record, "create_struct_failed", f"Failed to create struct {param_type} at {record.address:x}.\n")
            return False
    else:
        if not idaapi.create_data(record.address, dt_flag, dt_sz, idaapi.BADNODE):
            _add_bad_record(record, "create_data_failed", f"Failed to create data type {param_type} at {record.address:x}.\n")
            return False

    idaapi.set_name(record.address, record.name, idaapi.SN_NOWARN)
    idaapi.set_cmt(record.address, record.comment, True)
    idaapi.msg(f"- Parameter at {record.address:x} created, named to {record.name}.\n")

    return True


def _import_table(record: Record, records: list[Record], addr2idx: dict[int, int]) -> bool:
    """导入类型为 "数表" 的记录"""
    table_type = record.info.get("type", None)  # data_type[array_size]
    if not table_type:
        _add_bad_record(record, "no_table_type", f"Table at {record.address:x} has no type info. Skipped.\n")
        return False

    # 解析 data_type 和 array_size
    pattern = r"(\w+)\[(\d+)\]"
    m = re.match(pattern, table_type)
    if not m:
        _add_bad_record(record, "invalid_table_type", f"Invalid table type: {table_type}. Skipped.\n")
        return False
    dt_str, array_size = m.groups()
    array_size = int(array_size)
    dt_flag, dt_sz = get_data_flags_size(dt_str)
    if dt_sz == -1:
        _add_bad_record(record, "unsupported_data_type", f"Unsupported data type: {dt_str}. Skipped.\n")
        return False

    # 去掉原有定义
    idaapi.del_items(record.address, idaapi.DELIT_SIMPLE, array_size * dt_sz)

    # 首先定义 record.address 处的数据结构
    is_struct_array = dt_str.startswith("struct_")
    if is_struct_array:  # 结构体数组
        tid = idaapi.get_struc_id(dt_str)
        if not idaapi.create_struct(record.address, dt_sz, tid):
            _add_bad_record(record, "create_struct_failed", f"Failed to create struct {dt_str} at {record.address:x}.\n")
            return False
    else:
        if not idaapi.create_data(record.address, dt_flag, dt_sz, idaapi.BADNODE):
            _add_bad_record(record, "create_data_failed", f"Failed to create data type {dt_str} at {record.address:x}.\n")
            return False

    # 一些选项
    no_array = record.check_opt("no_array")  # 不作为一个整体创建数组
    no_append_cmt = record.check_opt("no_append_cmt")  # 不追加注释

    # 创建数组
    is_small_array = array_size <= 100 or array_size * dt_sz < 0x1000  # 小数组
    create_struct_array = not no_array and is_small_array
    if create_struct_array:  # 作为一个整体创建数组
        if not idc.make_array(record.address, array_size):
            _add_bad_record(record, "create_array_failed", f"Failed to create array at {record.address:x}.\n")
            return False
        # 设置数组参数
        ap = idaapi.array_parameters_t()
        ap.flags = idaapi.AP_INDEX | idaapi.AP_ARRAY
        if record.check_opt("idxhex"):
            ap.flags |= idaapi.AP_IDXDEC  # 默认十进制
        else:
            ap.flags |= idaapi.AP_IDXHEX
        ap.lineitems = 0 if is_struct_array else 1
        if record.info.get("lineitems", None):
            ap.lineitems = int(record.info["lineitems"])
        idaapi.set_array_parameters(record.address, ap)
    else:  # 逐个创建数组元素
        for i in range(1, array_size):
            addr = record.address + i * dt_sz
            if is_struct_array:
                if not idaapi.create_struct(addr, tid):
                    _add_bad_record(record, "create_struct_failed", f"Failed to create struct {dt_str} at {addr:x}.\n")
                    continue
            else:
                if not idc.create_data(addr, dt_flag, dt_sz, idaapi.BADNODE):
                    _add_bad_record(record, "create_data_failed", f"Failed to create data type {dt_str} at {addr:x}.\n")
                    continue

            # 设置数组元素注释
            if not no_append_cmt:
                if dt_str == "address":
                    idaapi.set_cmt(addr, "", True)  # 每个地址元素的 repeatable comment 不应被覆盖
                else:
                    idaapi.set_cmt(addr, f"{record.name}[{i}]", True)

                # 一些额外处理

                # 如果元素为地址，则对指向的地址附加 repeatable 注释
                if dt_str == "address":
                    dst_addr = idaapi.get_wide_dword(addr)
                    dst_func = idaapi.get_func(dst_addr)
                    if dst_func and dst_func.start_ea == dst_addr:  # 函数首地址
                        dst_comment = idaapi.get_func_cmt(dst_func, True) or ""
                        add_comment = f"[{record.name}+{4*i:x}]"
                        if not no_append_cmt and add_comment not in dst_comment:
                            dst_comment += " " + add_comment
                            idaapi.set_func_cmt(dst_addr, dst_comment, True)
                        # 更新函数记录
                        if dst_addr in addr2idx:
                            dst_record = records[addr2idx[dst_addr]]
                            dst_record.comment = dst_comment
                        else:
                            new_record = Record(dst_addr, "函数", comment=dst_comment)
                            records.append(new_record)
                            addr2idx[dst_addr] = len(records) - 1
                    else:  # 非函数
                        dst_comment = idaapi.get_cmt(dst_addr, True) or ""
                        add_comment = f"[{record.name}+{4*i:x}]"
                        if not no_append_cmt and add_comment not in dst_comment:
                            dst_comment += " " + add_comment
                            idaapi.set_cmt(dst_addr, dst_comment, True)
                        # 更新地址记录
                        if dst_addr in addr2idx:
                            dst_record = records[addr2idx[dst_addr]]
                            dst_record.comment = dst_comment
                        else:
                            new_record = Record(dst_addr, "地址", comment=dst_comment)
                            records.append(new_record)
                            addr2idx[dst_addr] = len(records) - 1

    # 补充数组信息
    array_detail = f"[end={record.address+dt_sz*array_size:x},size={array_size},item_size={dt_sz:#x}]"
    if not record.comment.endswith(array_detail):
        record.comment += " " + array_detail

    idaapi.set_name(record.address, record.name, idaapi.SN_NOWARN)
    idaapi.set_cmt(record.address, record.comment, True)
    idaapi.msg(f"- Table at {record.address:x} created, size: {array_size}, named to {record.name}.\n")

    return True


def import_records():
    """读取 records_file 中的记录，导入到 IDA 中, 并将更新的信息保存到 records_file 中"""
    records_file = MEM_RECORDS_FILE

    idaapi.msg("-" * 80 + "\n")
    idaapi.msg(f"Importing records from {records_file}...\n")

    records, addr2idx = collect_records(records_file)

    for record_index in range(len(records)):
        record = records[record_index]
        match (record.type):

            case "函数":  # 1. 如果为函数开头地址，更新函数名称和注释
                _import_function(record)
            case "参数":  # 2. 如果为参数，创建数据
                _import_parameter(record)
            case "数表":  # 3. 如果为数表，创建数组
                _import_table(record, records, addr2idx)
            case _:  # 其他情况，更新地址名称和注释
                idaapi.set_name(record.address, record.name, idaapi.SN_NOWARN)
                idaapi.set_cmt(record.address, record.comment, True)
                idaapi.msg(f"- Address at {record.address:x} name updated to {record.name}.\n")

    # 刷新 IDA 视图和 反编译视图
    window_refresh_flags = idaapi.IWID_DISASM | idaapi.IWID_PSEUDOCODE
    idaapi.request_refresh(window_refresh_flags)
    idaapi.refresh_idaview()

    # 按(类别，地址)排序
    sort_records(records)

    # 找出地址重复的记录
    addr_set = set()
    duplicate_records = []
    for record in records:
        if record.address in addr_set:
            duplicate_records.append(record)
            # records.remove(record)
        addr_set.add(record.address)
    idaapi.msg(f"Found {len(duplicate_records)} duplicate records:\n")
    for record in duplicate_records:
        idaapi.msg(f"- {record.address:x} {record.name}\n")
    print()

    # 输出有问题的记录
    _print_bad_records()
    print()

    # 保存记录
    save_records(records, records_file)

    idaapi.msg(f"Records imported from {records_file}.\n")
    idaapi.msg("Done.\n")
    idaapi.msg("-" * 80 + "\n")


def export_records():
    """读取 IDA 中已知内存地址记录的名称和注释等信息，导出到 records_file 中"""
    records_file = MEM_RECORDS_FILE

    idaapi.msg("-" * 80 + "\n")
    idaapi.msg(f"Exporting records to {records_file}...\n")

    # 读取记录
    records, addr2idx = collect_records(records_file)

    for record in records:
        match (record.type):
            case "函数":
                func = idaapi.get_func(record.address)
                if func and func.start_ea == record.address:
                    name = idaapi.get_func_name(record.address)
                    if not is_auto_generated_name(name):
                        record.name = name
                    record.comment = idaapi.get_func_cmt(record.address, True) or ""
                    # 更新函数声明
                    decl = idaapi.print_type(record.address, idaapi.PRTYPE_1LINE | idaapi.NTF_CHKSYNC)
                    if decl:
                        record.info["decl"] = decl
            case _:
                name = idaapi.get_name(record.address)
                if not is_auto_generated_name(name):
                    record.name = name
                record.comment = idaapi.get_cmt(record.address, True) or ""

    # 找出 IDA functions 中其他带注释的函数（带注释表示在 IDA 对该函数做了记录）
    for func_ea in idautils.Functions():
        if func_ea not in addr2idx:
            cmt = idaapi.get_func_cmt(func_ea, True)
            if not cmt:
                continue
            # 去掉一些特例
            if any(cmt.startswith(prefix) for prefix in ("Microsoft", "MFC", "?")):
                continue
            record = Record(func_ea, "函数", comment=cmt)
            name = idaapi.get_func_name(func_ea)
            if not is_auto_generated_name(name):
                record.name = name
            # 更新函数声明
            decl = idaapi.print_type(func_ea, idaapi.PRTYPE_1LINE | idaapi.NTF_CHKSYNC)
            if decl:
                record.info["decl"] = decl
            records.append(record)
            addr2idx[func_ea] = len(records) - 1

            idaapi.msg(f"Function at {func_ea:x} exported.\n")

    # 找出其他带注释的 Names
    for ea, name in idautils.Names():
        if ea not in addr2idx:
            cmt = idaapi.get_cmt(ea, True)
            if not cmt:  # 如果没有注释，跳过
                continue
            elif any(name.startswith(prefix) for prefix in ("a", "stru", "__", "unknown_")):  # 强制跳过某些前缀开头的名称
                continue
            elif is_chinese(cmt[0]):  # 注释第一个字符是中文的保留
                pass
            elif any(name.startswith(prefix) for prefix in ("sub_", "loc_", "j_", "def_")):  # 强制跳过某些前缀开头的名称
                continue
            elif any(cmt.startswith(prefix) for prefix in ("Microsoft", "MFC", "?", "D3DX", "jumptable")):
                continue

            record = Record(ea, "地址", name, cmt)
            records.append(record)
            addr2idx[ea] = len(records) - 1

            idaapi.msg(f"Name {name} at {ea:x} exported.\n")

    # 按(类别，地址)排序
    sort_records(records)

    # 保存记录
    save_records(records, records_file)

    idaapi.msg(f"{len(records)} records exported to {records_file}.\n")
    idaapi.msg("Done.\n")
    idaapi.msg("-" * 80 + "\n")


def action():
    # 交互式选择导入或导出
    button = idaapi.ask_buttons("Import", "Export", "Cancel", 1, "Import or export memory records")
    if button == 1:
        import_records()
    elif button == 0:
        export_records()
    else:
        idaapi.msg("Canceled.\n")


##########################################################################
###                        IDA Plugin 接口相关                           ###
##########################################################################


class San11MemPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC
    comment = "Import or export memory records (@san11pk)."
    help = "Alt-Shift-M to import or export san11pk memory records."
    wanted_name = "San11MemPlugin"
    wanted_hotkey = "Alt-Shift-M"

    ACTION_IMPORT = "san11:import_memory"
    ACTION_EXPORT = "san11:export_memory"

    def init(self):
        # 注册 import action
        import_action_desc = idaapi.action_desc_t(
            self.ACTION_IMPORT,
            "Import memory records",
            IDACtxEntry(import_records),
            "Shift-I",
            "Import memory records (@san11pk)",
            0,
        )
        assert idaapi.register_action(import_action_desc), "Failed to register action: import"
        # 注册 export action
        export_action_desc = idaapi.action_desc_t(
            self.ACTION_EXPORT,
            "Export memory records",
            IDACtxEntry(export_records),
            "Shift-E",
            "Export memory records (@san11pk)",
            0,
        )
        assert idaapi.register_action(export_action_desc), "Failed to register action: export"

        idaapi.msg("San11MemPlugin initialized.\n")
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        action()

    def term(self):
        idaapi.unregister_action(self.ACTION_IMPORT)
        idaapi.unregister_action(self.ACTION_EXPORT)
        idaapi.msg("San11MemPlugin terminated.\n")


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


def PLUGIN_ENTRY():
    return San11MemPlugin()


if __name__ == "__main__":
    action()


repository: xpko/xpko.github.io
file: IDA/plugins/export_source_path.py
description: 常用工具使用记录&填坑
wanted_name: export_source_path
comment: export_source_path
created_at: 2020-06-19 09:28:08+00:00
pushed_at: 2025-04-22 04:27:42+00:00
url:
https://github.com/xpko/xpko.github.io/blob/e1fa780a92288dc2d492e0742c91b1edf502d89d/IDA/plugins/export_source_path.py

content:
# -*- coding:utf-8 -*-
import os
import re

from idaapi import plugin_t
from idaapi import PLUGIN_PROC
from idaapi import PLUGIN_OK
import ida_nalt
import idaapi
import idautils
import idc
import time
from pathlib import Path


# 获取SO文件名和路径
def getSoPathAndName():
    fullpath = ida_nalt.get_input_file_path()
    filepath, filename = os.path.split(fullpath)
    return filepath, filename


# 获取代码段的范围
def getSegAddr():
    textStart = []
    textEnd = []

    for seg in idautils.Segments():
        if (idc.get_segm_name(seg)).lower() == '.text' or (
                idc.get_segm_name(seg)).lower() == 'text':
            tempStart = idc.get_segm_start(seg)
            tempEnd = idc.get_segm_end(seg)

            textStart.append(tempStart)
            textEnd.append(tempEnd)

    return min(textStart), max(textEnd)


def create_file(path: str):
    try:
        if os.path.exists(path):
            return
        if path[-1] == '/':
            if not os.path.exists(path):
                os.makedirs(path)
            return
        file_path = Path(path)
        if not file_path.parent.exists():
            file_path.parent.mkdir(parents=True, exist_ok=True)
        file_path.touch(exist_ok=True)
    except Exception as e:
        print(e)



def txt_to_path():
    dir = "./"
    for line in open(dir + "source_path.txt"):
        create_file(dir + line.strip())


def get_so_path_and_name():
    fullpath = ida_nalt.get_input_file_path()
    filepath, filename = os.path.split(fullpath)
    return filepath, filename


class PluginMain(plugin_t):
    flags = PLUGIN_PROC
    comment = "export_source_path"
    help = ""
    wanted_name = "export_source_path"
    wanted_hotkey = ""

    def init(self):
        print("export_source_path(v0.1) plugin has been loaded.")
        return PLUGIN_OK

    def run(self, arg):
        so_path,so_name = get_so_path_and_name()
        out_dir=so_path+os.sep+so_name+"_source_path"+os.sep
        sc = idaapi.string_info_t()
        for i in range(0, idaapi.get_strlist_qty()):
            idaapi.get_strlist_item(sc, i)
            text = idaapi.get_strlit_contents(sc.ea, sc.length, sc.type).decode()
            if re.compile(r"""^(?:
        (?=.*?/)                                # 有斜杠的情况
        (?:\.\.?/)*[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*/*

        |

        (?!.*?/)                                # 无斜杠的情况
        [A-Za-z0-9._-]+\.[A-Za-z0-9]+
    )$""",re.VERBOSE).match(text):
                create_file(out_dir + text.strip())
                print("create file: ", text)
        print("The source path dir: %s" % out_dir)

    def term(self):
        pass


def PLUGIN_ENTRY():
    return PluginMain()


repository: m223744/MalwareAnalysisCapstone
file: mainCode/ghidraApi/ghidra/Extensions/IDAPro/Python/6xx/plugins/xmlldr.py
description: Automated Static Malware Analysis Capstone
wanted_name: XML Importer
comment: Import XML PROGRAM file
created_at: 2020-09-28 13:36:25+00:00
pushed_at: 2022-04-01 17:19:05+00:00
url:
https://github.com/m223744/MalwareAnalysisCapstone/blob/78f293a154b2ae8b3c7dc0fe01626b440f2ee69f/mainCode/ghidraApi/ghidra/Extensions/IDAPro/Python/6xx/plugins/xmlldr.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.
##
#---------------------------------------------------------------------
# xmlldr.py - IDA XML Import loader and plugin
#---------------------------------------------------------------------
"""
Loader and plugin for IDA to import a XML PROGRAM file to a database.
"""

import idaapi
import sys
import time
from xml.etree import cElementTree


XML_IMPORTER_VERSION = "2.1.1"
BASELINE_IDA_VERSION = 620
IDA_SDK_VERSION = idaapi.IDA_SDK_VERSION
BADADDR = idaapi.BADADDR


"""
Loader functions
"""
def accept_file(li, n):
    """
    Check if the file is of supported format

    @param li: a file-like object which can be used to access the input data
    @param n : format number. The function will be called with incrementing
                number until it returns zero
    @return: 0 - no more supported formats
                string "name" - format name to display in the chooser dialog
                dictionary { 'format': "name", 'options': integer }
                options: should be 1, possibly ORed with ACCEPT_FIRST (0x8000)
                to indicate preferred format
    """
    if IDA_SDK_VERSION < BASELINE_IDA_VERSION:
        return 0
    # we support only one format per file
    if n > 0: return 0
    # read 16K bytes to allow for the DTD
    data = li.read(0x4000)
    # look for start of <PROGRAM> element
    start = data.find("<PROGRAM")
    if start >= 0: return "XML PROGRAM file"
    return 0


def load_file(li, neflags, format):
    """
    Load the file into database

    @param li: a file-like object which can be used to access the input data
    @param neflags: options selected by the user, see loader.hpp
    @return: 0-failure, 1-ok
    """
    global event, element
    plugin = False
    status = 0
    st = idaapi.setStat(idaapi.st_Work)
    xml = XmlImporter(False, 0)
    try:
        status = xml.import_xml()
    except Cancelled:
        msg = "XML PROGRAM import cancelled!"
        print "\n" + msg
        idaapi.warning(msg)
    except MultipleAddressSpacesNotSupported:
        msg  = "XML Import cancelled!"
        msg += "\n\nXML Import does not currently support"
        msg += "\nimporting multiple address spaces."
        print "\n" + msg
        idaapi.warning(msg)
    except:
        print "\nHouston, we have a problem!"
        msg = "***** Exception occurred: XML loader failed! *****"
        print "\n" + msg + "\n", sys.exc_type, sys.exc_value
        print event, element.tag, element.attrib
        idaapi.warning(msg)
    finally:
        idaapi.setStat(st)
        xml.cleanup()
        return status


class XmlImporterPlugin(idaapi.plugin_t):
    """
    XML Importer plugin class
    """
    flags = 0
    comment = "Import XML PROGRAM file"
    help = "Import XML <PROGRAM> document to database"
    wanted_name = "XML Importer"
    wanted_hotkey = "Ctrl-Alt-l"

    def init(self):
        """
        init function for XML Importer plugin.
        
        Returns:
            Constant PLUGIN_OK if this IDA version supports the plugin,
            else returns PLUGIN_SKIP if this IDA is older than the supported
            baseline version.
        """
        if IDA_SDK_VERSION < BASELINE_IDA_VERSION:
            idaapi.msg('\nXML Importer plugin (xmlldr.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 Importer plugin.
        
        Args:
            arg: Integer, a non-zero value enables auto-run feature for
                 IDA batch (no gui) processing mode. Default is 0.
        """
        st = idaapi.setStat(idaapi.st_Work)
        xml = XmlImporter(True, arg)
        try:
            try:
                xml.import_xml()
            except Cancelled:
                msg = "XML Import cancelled!"
                print "\n" + msg
                idaapi.warning(msg)
            except MultipleAddressSpacesNotSupported:
                msg  = "XML Import cancelled!"
                msg += "\n\nXML Import does not currently support"
                msg += "\nimporting multiple address spaces."
                print "\n" + msg
                idaapi.warning(msg)
            except:
                msg = "***** Exception occurred: XML Importer failed! *****"
                print "\n" + msg + "\n", sys.exc_type, sys.exc_value
                idaapi.warning(msg)
        finally:
            xml.cleanup()
            idaapi.setStat(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlImporterPlugin()


class Cancelled(Exception):
    pass


class FileError(Exception):
    pass


class MultipleAddressSpacesNotSupported(Exception):
    pass


class XmlImporter:
    """
    XmlImporter class contains methods to import an XML PROGRAM
        document into IDA.
    """
    def __init__(self, plugin, arg):
        """
        Initializes the XmlImporter attributes

        Args:
            arg: Integer, non-zero value enables auto-run feature for
                IDA batch (no gui) processing mode. Default is 0.
        """
        self.plugin = plugin
        self.autorun = False
        self.debug = False # set this to True for debug prints
        self.Elements = {}
        self.Counters = []
        self.Tags = []
        self.Timers = dict()
        self.xmlfile = 0
        self.addr_mode = 1
        self.create = True
        self.dataseg = None
        self.deferred = []
        self.callbacks = {
            'start' : {
                BOOKMARKS           : self.update_import,
                CODE                : self.update_import,
                COMMENTS            : self.update_import,
                COMPILER            : self.import_compiler,
                DATA                : self.update_import,
                DATATYPES           : self.update_import,
                EQUATES             : self.update_import,
                FUNCTIONS           : self.update_import,
                INFO_SOURCE         : self.import_info_source,
                MARKUP              : self.update_import,
                MEMORY_MAP          : self.import_memory_map,
                PROCESSOR           : self.import_processor,
                PROGRAM             : self.import_program,
                PROGRAM_ENTRY_POINTS: self.update_import,
                REGISTER_VALUES     : self.update_import,
                SYMBOL_TABLE        : self.update_import },
            'end' : {
                BOOKMARK            : self.import_bookmark,
                CODE_BLOCK          : self.import_codeblock,
                COMMENT             : self.import_comment,
                DEFINED_DATA        : self.import_defined_data,
                ENUM                : self.import_enum,
                EQUATE_GROUP        : self.import_equate_group,
                EQUATE_REFERENCE    : self.import_equate_reference,
                FUNCTION            : self.import_function,
                FUNCTION_DEF        : self.import_function_def,
                MANUAL_INSTRUCTION  : self.import_manual_instruction,
                MANUAL_OPERAND      : self.import_manual_operand,
                MEMORY_REFERENCE    : self.import_memory_reference,
                MEMORY_SECTION      : self.import_memory_section,
                PROGRAM_ENTRY_POINT : self.import_program_entry_point,
                REGISTER_VALUE_RANGE: self.import_register_value_range,
                STACK_REFERENCE     : self.import_stack_reference,
                STRUCTURE           : self.import_structure,
                SYMBOL              : self.import_symbol,
                TYPE_DEF            : self.import_typedef,
                UNION               : self.import_union,
                # end element for elapse time
                BOOKMARKS           : self.display_timer,
                CODE                : self.display_timer,
                COMMENTS            : self.display_timer,
                DATA                : self.display_timer,
                DATATYPES           : self.process_deferred,
                EQUATES             : self.display_timer,
                FUNCTIONS           : self.display_timer,
                MARKUP              : self.display_timer,
                MEMORY_MAP          : self.display_timer,
                PROGRAM             : self.display_total_time,
                PROGRAM_ENTRY_POINTS: self.display_timer,
                REGISTER_VALUES     : self.display_timer,
                SYMBOL_TABLE        : self.display_timer }
            }


    def import_xml(self):
        """
        Imports the XML PROGRAM file into the database.
        """
        self.display_xml_importer_version()
        displayMenu = self.plugin == True and self.autorun == False 
        #displayMenu = self.autorun == False 
        self.get_options(displayMenu)
        if self.plugin:
            self.filename=idaapi.askfile_c(0, "*.xml",
                                        "Enter name of xml file:")
        else:
            self.filename = idaapi.get_input_file_path()
        if (len(self.filename) == 0):
            return
        idaapi.msg('\nImporting from: ' + self.filename + '\n') 
        idc.Wait()
        if self.plugin == False: idaapi.hide_wait_box()
        idaapi.show_wait_box("Importing XML PROGRAM document....")
        n = 0
        for event,element in cElementTree.iterparse(self.filename,
                                                events=("start","end")):
            if idaapi.wasBreak() == True:
                raise Cancelled
            
            if self.debug == True and event == 'start':
                print element.tag, element.attrib, element.text
            
            if event in self.callbacks:
                if element.tag in self.callbacks[event]:
                    if event == 'start':
                        self.Timers[element.tag] = time.clock()
                    self.callbacks[event][element.tag](element)
                    if event == 'end':
                        element.clear()
            if event == 'end':
                n += 1
        end = time.clock()
        idaapi.hide_wait_box()
        self.display_summary()
        idaapi.msg('\nXML Elements parsed: ' + str(n) + '\n\n')
        return 1
    

    def get_options(self, display):
        """
        Displays the options menu and retrieves the option settings. 
        """
        fmt =  "HELP\n"
        fmt += "XML PROGRAM loader/importer plugin (Python)\n"
        fmt += "IDA SDK: "+ str(IDA_SDK_VERSION) + "\n\n"
        fmt +=  "The XML PROGRAM loader loads elements from a "
        fmt +=  "XML <PROGRAM> document to create an idb database.\n\n"
        fmt +=  "ENDHELP\n"
        fmt +=  "Import from XML PROGRAM document...."
        fmt += "\n <##Options##Code Blocks:{CodeBlocks}>"
        fmt += "\n <Entry Points:{EntryPoints}>"
        fmt += "\n <Segment Register Value Ranges:{RegisterValues}>"
        fmt += "\n <Data Types:{DataTypes}>"
        fmt += "\n <Data Definitions:{DataDefinitions}>"
        fmt += "\n <Symbols:{Symbols}>"
        fmt += "\n <Comments:{Comments}>"
        fmt += "\n <Bookmarks:{Bookmarks}>"
        fmt += "\n <Functions:{Functions}>"
        fmt += "\n <Memory References:{MemoryReferences}>"
        fmt += "\n <Equate/Enum References:{EquateReferences}>"
        fmt += "\n <Manual Instructions/Operands:{Manual}>{cGroup1}>"
        fmt += "\n\n"
    
        Opts = { 'cGroup1': idaapi.Form.ChkGroupControl(
                ( 
                "CodeBlocks", "EntryPoints", "RegisterValues",
                "DataTypes", "DataDefinitions",
                "Symbols", "Comments", "Bookmarks",
                "Functions", "MemoryReferences", "EquateReferences",
                "Manual")) }
        
        self.Options = idaapi.Form(fmt, Opts)
        self.Options.Compile()
    
        self.Options.CodeBlocks.checked       = True
        self.Options.EntryPoints.checked      = True
        self.Options.RegisterValues.checked   = True
        self.Options.DataTypes.checked        = True
        self.Options.DataDefinitions.checked  = True
        self.Options.Symbols.checked          = True
        self.Options.Functions.checked        = True
        self.Options.Comments.checked         = True
        self.Options.Bookmarks.checked        = True
        self.Options.MemoryReferences.checked = True
        self.Options.EquateReferences.checked = True
        self.Options.Manual.checked           = True
        
        if display == True:
            ok = self.Options.Execute()
            if (ok == 0):
                raise Cancelled
            
    def cleanup(self):
        """
        Frees memory and closes message box at termination.
        """
        if self.plugin:
            self.Options.Free()
        idaapi.hide_wait_box()
    

    def display_timer(self, element):
        """
        Displays the elapsed processing time for XML elements.
        
        Args:
            element: XML element object value containing the element tag.
        """
        if element.tag == MEMORY_MAP and self.plugin:
            return
        if element.tag in self.Timers:
            idaapi.msg('elapsed time: %.4f' %
                    (time.clock()-self.Timers[element.tag]))
    

    def display_total_time(self, element):
        """
        Displays the total processing time.
        
        Args:
            element: XML element object value (not used).
        """
        TOTAL = 'Total '
        idaapi.msg('\n%35selapsed time: %.4f' %
                (TOTAL,time.clock()-self.Timers[PROGRAM]))
    

    def display_summary(self):
        """
        Displays summary of the XML PROGRAM import in IDA output window.
        """
        summary = ''
        total = 0
        for tag in self.Tags:
            count = self.Counters[self.Elements[tag]]
            summary += "\n%s: %d" % (tag, count)
            total += count
        summary += '\n--------------------------------------'
        header = '\n--------------------------------------'
        header += ('\nTotal XML Elements Processed: %d') % total
        summary = header + summary
        idaapi.msg(summary)
        if self.plugin and self.autorun == False:
            frmt  = "TITLE XML Import Successful!\n"
            frmt += "ICON INFO\n"
            frmt += "AUTOHIDE NONE\n"
            frmt += "HIDECANCEL\n"
            firstline = "\n XML IMPORT SUCCESSFUL!"
            fileline = '\n\nFile: %s' % self.filename
            details = '\n\nSee output window for details...'
            idaapi.info("%s" % (frmt + firstline + fileline + details))
    

    def display_xml_importer_version(self):
        """
        Displays XML Importer plugin version info in IDA output window.
        """
        if self.plugin:
            f = idaapi.idadir(idaapi.PLG_SUBDIR) + '/xmlldr.py'
        else:
            f = idaapi.idadir(idaapi.LDR_SUBDIR) + '/xmlldr.py'
        plugintime = time.localtime(os.path.getmtime(f))
        ts = time.strftime('%b %d %Y %H:%M:%S', plugintime)
        version = "\nXML Importer Version " + XML_IMPORTER_VERSION
        version += " : SDK " + str(IDA_SDK_VERSION)
        version += " : Python : "+ ts + '\n'
        idaapi.msg(version)
    

    def get_address(self, element, attr):
        """
        Returns the address value for an element.
        
        Args:
            element: XML element object.
            attr: String containing the address attribute name.
            
        Returns:
            Numeric value representing the address.
        """
        addrstr = element.get(attr)
        if '::' in addrstr:
            # overlayed addresses not currently handled
            return BADADDR
        elif ':' in addrstr:
            [segstr, offset_str] = string.split(addrstr,':')
            offset = int(offset_str,16)
            if self.is_int(segstr) == True:
                sgmt = int(segstr,16)
                addr = (sgmt << 4) + offset
            else:
                # multiple address spaces not currently implemented
                addr = BADADDR
            return addr
        else:
            return int(element.get(attr), 16)
    

    def get_attribute(self, element, attr):
        """
        Returns the attribute value string.
        
        Args:
            element: XML element object.
            attr: String containing the attribute name.
            
        Returns:
            String representing the attribute value.
        """
        return element.get(attr)
    

    def get_attribute_value(self, element, attr):
        """
        Returns the numeric attribute value.
        
        Args:
            element: XML element object.
            attr: String containing the attribute name.
            
        Returns:
            Numeric value representing the attribute value.
        """
        val = element.get(attr)
        try:
            if val.upper().startswith('0X') or val.upper().startswith('-0X'):
                return int(val, 16)
            return int(val)
        except:
            idaapi.msg('\nUnable to decode string as value: ' + val)
            return 0
        

    def get_cbsize(self):
        """
        Returns the size of the addressable codebyte for the processor.
        
        Returns:
            Integer representing the number of 8-bit bytes in an
            addressable codebyte.
        """
        return (idaapi.ph_get_cnbits()+7)/8
    

    def get_datatype_flags(self, datatype, size):
        """
        Returns the flags bitmask for the datatype.
        
        Args:
            datatype: String representing the datatype.
            size: Integer representing the datatype size.

        Returns:
            Integer representing the bitmask.
        """
        if datatype.lower().startswith("byte"):     return idaapi.byteflag()
        if datatype.lower().startswith("word"):     return idaapi.wordflag()
        if datatype.lower().startswith("dword"):    return idaapi.dwrdflag()
        if datatype.lower().startswith("qword"):    return idaapi.qwrdflag()
        if datatype.lower().startswith("oword"):    return idaapi.owrdflag()
        if datatype.lower().startswith("tbyte"):    return idaapi.tbytflag()
        if datatype.lower().startswith("float"):    return idaapi.floatflag()
        if datatype.lower().startswith("double"):   return idaapi.doubleflag()
        if datatype.lower().startswith("packed"):   return idaapi.packrealflag()
        if self.is_string_type(datatype):           return idaapi.asciflag()
        if self.is_enumeration(datatype):           return idaapi.enumflag()
        if self.is_structure(datatype):             return idaapi.struflag()
        #if size == 4:                               return idaapi.dwrdflag()
        return 0
    

    def get_string_type(self, datatype):
        if datatype.lower() == 'mbcstring':
            return idaapi.ASCSTR_UNICODE
        if datatype.lower().find('unicode') != -1:
            if datatype.lower().find('pascal') != -1:
                return idaapi.ASCSTR_ULEN2
            return idaapi.ASCSTR_UNICODE
        if datatype.lower().find('pascal') != -1:
            return idaapi.ASCSTR_LEN2
        return idaapi.ASCSTR_TERMCHR
            
    
    def has_attribute(self, element, attr):
        """
        Returns true if the XML element contains the named attribute.
        
        Args:
            element: XML element object
            attr: String containing name of the attribute
        
        Returns:
            True if the element contains the named attribute, otherwise False.
        """
        return attr in element.attrib
    

    def is_enumeration(self, datatype):
        """
        Returns true if datatype is an existing enumeration in the database.
        
        Args:
            datatype: String representing the datatype.
            
        Returns:
            True if the datatype is an enumeration in the database,
            otherwise False.
        """
        if idaapi.get_enum(datatype) == idaapi.BADNODE:  return False
        return True
    
    
    def is_int(self, s):
        try:
            int(s, 16)
            return True
        except:
            return False
        

    def is_pointer_type(self, dtype):
        """
        Returns true if the datatype represents a pointer.
        
        Args:
            dtype: String representing the datatype.
            
        Returns:
            True if the datatype represents a pointer, otherwise False.
        """
        if dtype.lower().startswith("pointer") or dtype.endswith('*'):
            return True
        return False
    

    def is_string_type(self, datatype):
        """
        Returns true if the datatype represents a string type.
        
        Args:
            datatype: String representing the datatype.
            
        Returns:
            True if the datatype represents a string, otherwise False.
        """
        if datatype.lower().startswith("unicode"):  return True
        if datatype.lower().startswith("string"):   return True
        return False
    

    def is_structure(self, datatype):
        """
        Returns true if the datatype represents a structure in the database.
        
        Args:
            dtype: String representing the datatype.
            
        Returns:
            True if the datatype represents an existing structure,
            otherwise False.
        """
        if idaapi.get_struc_id(datatype) == idaapi.BADNODE:  return False
        return True
    

    def import_address_range(self, address_range):
        """
        Processes ADDRESS_RANGE element.
        
        Args:
            address_range: XML element object containing start and end address
                attributes for the address range.
                
        Returns:
            Tuple containing two integers, the start and end address values.
        """
        start = self.get_address(address_range,START)
        end = self.get_address(address_range, END)
        self.update_counter(ADDRESS_RANGE)
        return (start, end)
    

    def import_bit_mask(self, bitmask, eid):
        """
        Processes a BIT_MASK element as an enum bitmask member.
        
        Args:
            bitmask: XML element object representing the IDA enum bitmask.
            eid: Integer representing the IDA enum id
        """
        if IDA_SDK_VERSION < 640:
            return
        name = self.get_attribute(bitmask,NAME)
        value = self.get_attribute_value(bitmask,VALUE)
        idaapi.set_bmask_name(eid, value, name)
        cid = idaapi.get_const_by_name(name)
        self.update_counter(BIT_MASK)
        regcmt = bitmask.find(REGULAR_CMT)
        if regcmt != None:
            idaapi.set_const_cmt(cid, regcmt.text, False);
            self.update_counter(BIT_MASK + ':' + REGULAR_CMT)
        rptcmt = bitmask.find(REPEATABLE_CMT)
        if rptcmt != None:
            idaapi.set_const_cmt(cid, rptcmt.txt, True);
            self.update_counter(BIT_MASK + ':' + REPEATABLE_CMT)
    

    def import_bookmark(self, bookmark):
        """
        Processes a BOOKMARK element.
        
        Args:
            bookmark: XML element object containing bookmark data.
        """
        if self.Options.Bookmarks.checked == False:
            return
        try:
            addr = self.get_address(bookmark, ADDRESS)
            if self.has_attribute(bookmark, TYPE):
                typ = self.get_attribute(bookmark, TYPE)
            category = ''
            if self.has_attribute(bookmark, CATEGORY):
                category = self.get_attribute(bookmark, CATEGORY)
            description = ''
            if self.has_attribute(bookmark, DESCRIPTION):
                description = self.get_attribute(bookmark, DESCRIPTION)
            if idaapi.isEnabled(addr) == False:
                msg = ("import_bookmark: address %X not enabled in database"
                       % addr)
                print msg
                return
            self.update_counter(BOOKMARK)
            for slot in range(1,1025):
                if IDA_SDK_VERSION != 695:
                    ea = idc.GetMarkedPos(slot)
                else:
                    import ida_moves
                    import ida_pro
                    curloc = ida_moves.curloc()
                    intp = ida_pro.int_pointer()
                    intp.assign(slot)
                    ea = curloc.markedpos(intp)
                if ea == BADADDR:
                    curloc = idaapi.curloc()
                    curloc.ea = addr
                    curloc.mark(slot, category, description)
                    break
        except:
            msg = "** Exception occurred in import_bookmark **"
            print "\n" + msg + "\n", sys.exc_type, sys.exc_value
    

    def import_cmts(self, element, sid, typ):
        """
        Processes REGULAR_CMT and REPEATABLE_CMT elements for structures.
        
        Args:
            element: XML element object containing a REGULAR_CMT or
                REPEATABLE_CMT element
            sid: Integer representing the structure id
            typ: String indicating structure type (STRUCTURE or UNION)
        """
        regcmt = element.find(REGULAR_CMT)
        if regcmt != None:
            idaapi.set_struc_cmt(sid, regcmt.text, False)
            self.update_counter(typ + ':' + REGULAR_CMT)
        rptcmt = element.find(REPEATABLE_CMT)
        if rptcmt != None:
            idaapi.set_struc_cmt(sid, rptcmt.text, True)
            self.update_counter(typ + ':' + REPEATABLE_CMT)
            
        
    def import_codeblock(self, code_block):
        """
        Processes a CODE_BLOCK element by disassembling the address range.
        
        Args:
            code_block: XML element containing codeblock start and end
                addresses.
        """
        if self.Options.CodeBlocks.checked == False:
            return
        start = self.get_address(code_block, START)
        end = self.get_address(code_block, END)
        idaapi.do_unknown_range(start, end-start+1, 3)
        addr = start
        while (addr <= end):
            length = idaapi.create_insn(addr)
            addr += idaapi.get_item_size(addr) * self.get_cbsize()
        self.update_counter(CODE_BLOCK)
    

    def import_comment(self, comment):
        """
        Processes a COMMENT element by creating the comment at the address.
        
        Args:
            comment: XML element containing the comment address, type,
                and text.
        """
        if self.Options.Comments.checked == False:
            return
        addr = self.get_address(comment, ADDRESS)
        ctype = self.get_attribute(comment,TYPE)
        text = comment.text
        if ctype == 'pre':
            idaapi.add_long_cmt(addr, True, text)
        elif ctype == 'end-of-line':
            idaapi.set_cmt(addr, text, False)
        elif ctype == 'repeatable':
            idaapi.set_cmt(addr, text, True)
        elif ctype == 'post':
            idaapi.add_long_cmt(addr, False, text)
        self.update_counter(COMMENT+':' + ctype)
    

    def import_compiler(self, compiler):
        """
        Processes the COMPILER element containing the compiler name.
        
        Args:
            compiler: XML element containing the compiler name.
        """
        name = self.get_attribute(compiler, NAME)
        comp = idaapi.COMP_UNK
        if   name == "Visual C++":      comp = idaapi.COMP_MS
        elif name == "Borland C++":     comp = idaapi.COMP_BC
        elif name == "Watcom C++":      comp = idaapi.COMP_WATCOM
        elif name == "GNU C++":         comp = idaapi.COMP_GNU
        elif name == "Visual Age C++":  comp = idaapi.COMP_VISAGE
        elif name == "Delphi":          comp = idaapi.COMP_BP
        idaapi.cvar.inf.cc.id = comp
        self.update_counter(COMPILER)
    

    def import_defined_data(self, defined_data):
        """
        Processes a DEFINED_DATA element by creating a data item at the
            specified address.
        
        Args:
            defined_data: XML element containing the address and
                datatype information for the data item
        """
        if self.Options.DataDefinitions.checked == False:
            return
        addr = self.get_address(defined_data, ADDRESS)
        datatype = self.get_attribute(defined_data, DATATYPE)
        size = self.get_attribute_value(defined_data, SIZE)
        self.update_counter(DEFINED_DATA)        
        ti = idaapi.opinfo_t()
        if self.is_pointer_type(datatype):
            #idaapi.set_refinfo(ti, 0, 0, 0, REF_OFF32)
            flag = idaapi.dwrdflag() | idaapi.FF_0OFF
            #idaapi.set_typeinfo(addr, 0, flag, ti)
        else:
            flag = self.get_datatype_flags(datatype, size)
        if flag == idaapi.asciflag():
            idaapi.make_ascii_string(addr, size,
                                     self.get_string_type(datatype))
        elif flag == idaapi.struflag():
            idaapi.doStruct(addr, size, idaapi.get_struc_id(datatype))
        else:
            idaapi.do_data_ex(addr, flag, size, idaapi.BADNODE)        
        typecmt = defined_data.find(TYPEINFO_CMT)
        if typecmt != None:
            self.update_counter(DEFINED_DATA + ':' + TYPEINFO_CMT)


    def import_enum(self, enum):
        """
        Processes an ENUM element by creating the enumeration.
        
        Args:
            enum: XML element containing the enumeration name and
                member data.
        """
        if self.Options.DataTypes.checked == False:
            return
        name = self.get_attribute(enum, NAME)
        if self.has_attribute(enum,NAMESPACE):
            namespace = self.get_attribute(enum, NAMESPACE)
        if self.has_attribute(enum,SIZE):
            size = self.get_attribute_value(enum, SIZE)
        eid = idaapi.add_enum(BADADDR, name,
                              idaapi.hexflag() | idaapi.dwrdflag())
        self.update_counter(ENUM)
        regcmt = enum.find(REGULAR_CMT)
        if regcmt != None:
            idaapi.set_enum_cmt(eid, regcmt.text, False)
            self.update_counter(ENUM + ':' + REGULAR_CMT)
        rptcmt = enum.find(REPEATABLE_CMT)
        if rptcmt != None:
            idaapi.set_enum_cmt(eid, rptcmt.text, True)
            self.update_counter(ENUM + ':' + REPEATABLE_CMT)
        display_settings = enum.find(DISPLAY_SETTINGS)
        if display_settings != None:
            self.update_counter(ENUM + ':' + DISPLAY_SETTINGS)
        enum_entries = enum.findall(ENUM_ENTRY)
        for enum_entry in enum_entries:
            self.import_enum_entry(enum_entry, eid)
    

    def import_enum_entry(self, enum_entry, eid):
        """
        Processes an ENUM_ENTRY by creating a member in the enumeration.
        
        Args:
            enum_entry: XML element containing the member name and value.
            eid: Integer representing the id of the enumeration.
        """
        name = self.get_attribute(enum_entry, NAME)
        value = self.get_attribute_value(enum_entry, VALUE)
        if IDA_SDK_VERSION > 630:
            idaapi.add_const(eid, name, value)
        else:
            idaapi.add_enum_member(eid, name, value)
        if IDA_SDK_VERSION > 630:
            cid = idaapi.get_const_by_name(name)
        else:
            cid = idaapi.get_enum_member_by_name(name)
        self.update_counter(ENUM_ENTRY)
        regcmt = enum_entry.find(REGULAR_CMT)
        if regcmt != None:
            if IDA_SDK_VERSION > 630:
                idaapi.set_const_cmt(cid, regcmt.text, False);
            else:
                idaapi.set_enum_member_cmt(cid, regcmt.text, False);
            self.update_counter(ENUM_ENTRY + ':' + REGULAR_CMT)
        rptcmt = enum_entry.find(REPEATABLE_CMT)
        if rptcmt != None:
            if IDA_SDK_VERSION > 630:
                idaapi.set_const_cmt(cid, rptcmt.text, True);
            else:
                idaapi.set_enum_member_cmt(cid, rptcmt.text, True);
            self.update_counter(ENUM_ENTRY + ':' + REPEATABLE_CMT)
    

    def import_equate(self, equate, eid):
        """
        Processes EQUATE element as member of an enumeration.
        
        Args:
            enum_entry: XML element containing the equate name and value.
            eid: Integer representing the id for the enumeration.
        """
        name = self.get_attribute(equate,NAME)
        value = self.get_attribute_value(equate,VALUE)
        bm = -1
        if self.has_attribute(equate, BIT_MASK):
            bm = self.get_attribute_value(equate, BIT_MASK)
        if IDA_SDK_VERSION > 630:
            #idaapi.add_const(eid, name, value, bm)
            idaapi.add_const(eid, name, value)
            cid = idaapi.get_const_by_name(name)
        else:
            #idaapi.add_enum_member(eid, name, value, bm)
            idaapi.add_enum_member(eid, name, value)
            cid = idaapi.get_enum_member_by_name(name)
        self.update_counter(EQUATE)
        regcmt = equate.find(REGULAR_CMT) 
        if regcmt != None:
            if IDA_SDK_VERSION > 630:
                idaapi.set_const_cmt(cid, regcmt.text, False);
            else:
                idaapi.set_enum_member_cmt(cid, regcmt.text, False);
            self.update_counter(EQUATE + ':' + REGULAR_CMT)
        rptcmt = equate.find(REPEATABLE_CMT)
        if rptcmt != None:
            if IDA_SDK_VERSION > 630:
                idaapi.set_const_cmt(cid, rptcmt.text, True);
            else:
                idaapi.set_enum_member_cmt(cid, rptcmt.text, True);
            self.update_counter(EQUATE + ':' + REPEATABLE_CMT)
    

    def import_equate_group(self, equate_group):
        """
        Processes EQUATE_GROUP as IDA enumeration type.
        
        Args:
            equate_group: XML element containing the group name and
                equate definitions.
        """
        if self.Options.DataTypes.checked == False:
            return
        msg = EQUATE_GROUP
        name = ''
        if self.has_attribute(equate_group, NAME):
            name = self.get_attribute(equate_group, NAME)
        bf = ''
        if self.has_attribute(equate_group, BIT_FIELD):
            bf = self.get_attribute(equate_group, BIT_FIELD)
        eid = idaapi.add_enum(BADADDR, name, idaapi.hexflag())
        idaapi.set_enum_bf(eid, (bf == 'yes'))
        self.update_counter(EQUATE_GROUP)
        regcmt = equate_group.find(REGULAR_CMT)
        if regcmt != None:
            idaapi.set_enum_cmt(eid, regcmt.text, False)
            self.update_counter(EQUATE_GROUP + ':' + REGULAR_CMT)
        rptcmt = equate_group.find(REPEATABLE_CMT)
        if rptcmt != None:
            idaapi.set_enum_cmt(eid, rptcmt.text, True)
            self.update_counter(EQUATE_GROUP + ':' + REPEATABLE_CMT)
        equates = equate_group.findall(EQUATE)
        for equate in equates:
            self.import_equate(equate,eid)
        bit_masks = equate_group.findall(BIT_MASK)
        for bit_mask in bit_masks:
            self.import_bit_mask(bit_mask, eid)
    

    def import_equate_reference(self, equate_reference):
        if (self.Options.DataTypes.checked == False or
            self.Options.EquateReferences.checked == False):
            return
        self.update_counter(EQUATE_REFERENCE)
        addr = self.get_address(equate_reference, ADDRESS)
        name = ''
        if self.has_attribute(equate_reference, NAME):
            name = self.get_attribute(equate_reference, NAME)
        if name == '':
            return
        opnd = 0
        if self.has_attribute(equate_reference, OPERAND_INDEX):
            opnd = self.get_attribute_value(equate_reference, OPERAND_INDEX)
        value = None
        if self.has_attribute(equate_reference, VALUE):
            value = self.get_attribute_value(equate_reference, VALUE)
        if IDA_SDK_VERSION < 650:
            cid = idaapi.get_enum_member_by_name(name)
        else:
            cid = idaapi.get_const_by_name(name)
        if cid == idaapi.BADNODE:
            return
        if IDA_SDK_VERSION < 650:
            eid = idaapi.get_enum_member_enum(cid)
        else:
            eid = idaapi.get_const_enum(cid)
        if eid == idaapi.BADNODE:
            return
        idaapi.op_enum(addr, opnd, eid, 0)
        

    def import_function(self, function):
        """
        Creates a function using the FUNCTION attributes.
        
        Args:
            function: XML element containing the function address and
                attributes.
        """
        if self.Options.Functions.checked == False:
            return
        try:
            entry_point = self.get_address(function, ENTRY_POINT)
            name = ''
            if self.has_attribute(function, NAME):
                name = self.get_attribute(function, NAME)
            libfunc = 'n'
            if self.has_attribute(function, LIBRARY_FUNCTION):
                libfunc = self.get_attribute(function, LIBRARY_FUNCTION)
            if idaapi.isEnabled(entry_point) == False:
                msg = ("import_function: address %X not enabled in database"
                       % entry_point)
                print msg
                return
            idaapi.add_func(entry_point, BADADDR)
            self.update_counter(FUNCTION)
            func = idaapi.get_func(entry_point)
            if libfunc == 'y':
                func.flags |= idaapi.FUNC_LIB
            ranges = function.findall(ADDRESS_RANGE)
            for addr_range in ranges:
                (start, end) = self.import_address_range(addr_range)
                idaapi.append_func_tail(func, start, end)
                #idaapi.analyze_area(start, end+1)
            idc.Wait()
            regcmt = function.find(REGULAR_CMT)
            if regcmt != None:
                self.update_counter(FUNCTION + ':' + REGULAR_CMT)
                idaapi.set_func_cmt(func, regcmt.text, False)
            rptcmt = function.find(REPEATABLE_CMT)
            if rptcmt != None:
                self.update_counter(FUNCTION + ':' + REPEATABLE_CMT)
                idaapi.set_func_cmt(func, rptcmt.text, True)
            typecmt = function.find(TYPEINFO_CMT)
            if typecmt != None:
                self.update_counter(FUNCTION + ':' + TYPEINFO_CMT)
                idc.SetType(entry_point, typecmt.text + ';')
            sf = function.find(STACK_FRAME)
            if sf != None:
                self.import_stack_frame(sf, func)
            register_vars = function.findall(REGISTER_VAR)
            for register_var in register_vars:
                self.import_register_var(register_var, func)
        except:
            msg = "** Exception occurred in import_function **"
            print "\n" + msg + "\n", sys.exc_type, sys.exc_value


    def import_function_def(self, function_def):
        # import_function_def: NOT IMPLEMENTED
        if self.Options.DataTypes.checked == False:
            return
        self.update_counter(FUNCTION_DEF)
        

    def import_info_source(self, info_source):
        """
        Processes INFO_SOURCE containing information about the
            source of the XML PROGRAM file.
        
        Args:
            info_source: XML element containing attributes that identify
                the source of the PROGRAM data.
        """
        if self.has_attribute(info_source, TOOL):
            tool = self.get_attribute(info_source, TOOL)
        if self.has_attribute(info_source, USER):
            user = self.get_attribute(info_source, USER)
        if self.has_attribute(info_source, FILE):
            f = self.get_attribute(info_source, FILE)
        if self.has_attribute(info_source, TIMESTAMP):
            ts = self.get_attribute(info_source, TIMESTAMP)
        self.update_counter(INFO_SOURCE)
    

    def import_manual_instruction(self, manual_instruction):
        """
        Creates a manual instruction.
        
        Args:
            manual_instruction: XML element containing MANUAL_INSTRUCTION.
        """
        if self.Options.Manual.checked == False:
            return
        addr = self.get_address(manual_instruction, ADDRESS)
        idaapi.set_manual_insn(addr, manual_instruction.text)
        self.update_counter(MANUAL_INSTRUCTION)
    

    def import_manual_operand(self, manual_operand):
        """
        Creates a manual operand at an address.
        
        Args:
            manual_operand: MANUAL_OPERAND XML element.
        """
        if self.Options.Manual.checked == False:
            return
        addr = self.get_address(manual_operand, ADDRESS)
        op = self.get_attribute_value(manual_operand, OPERAND_INDEX)
        if idaapi.isEnabled(addr):
            idaapi.set_forced_operand(addr, op, manual_operand.text)
            self.update_counter(MANUAL_OPERAND)


    def process_deferred(self, element):
        """
        Processes the list of deferred structure members when the
        DATATYPES end element is encountered.
        
        Args:
            element: XML end element for DATATYPES 
        """
        for (member, sptr) in self.deferred:
            self.import_member(member, sptr, False)
        self.display_timer(element)


    def import_member(self, member, sptr, defer=True):
        """
        Creates a member for a structure.
        
        Args:
            member: MEMBER XML element.
            sptr:
            defer:  boolean indicating if processing a member should be
                    deferred when the type is unknown. A member should
                    only be deferred on the first pass, not when processing
                    the deferred list.
        """
        offset = self.get_attribute_value(member, OFFSET)
        datatype = self.get_attribute(member, DATATYPE)
        if self.has_attribute(member, DATATYPE_NAMESPACE):
            dt_namespace = self.get_attribute(member, DATATYPE_NAMESPACE)
        name = ''
        if self.has_attribute(member, NAME):
            name = self.get_attribute(member, NAME)
        size = 0
        if self.has_attribute(member, SIZE):
            size = self.get_attribute_value(member, SIZE)
        ti = idaapi.opinfo_t()
        if self.is_pointer_type(datatype):
            #idaapi.set_refinfo(ti, 0, BADADDR, 0, REF_OFF32)
            flag = idaapi.dwrdflag() | idaapi.FF_0OFF
        else:
            flag = self.get_datatype_flags(datatype, size)
        if flag == 0:
            if defer:
                self.deferred.append([member, sptr])
                return                
        if flag == idaapi.enumflag():
            t = idaapi.get_enum(datatype)
            ti.ec.tid = t
            ti.ec.serial = idaapi.get_enum_idx(t)
        if flag == idaapi.struflag():
            t = idaapi.get_struc_id(datatype)
            ti.tid = t
        error = idaapi.add_struc_member(sptr, name, offset, flag, ti, size)
        mbr = idaapi.get_member(sptr, offset)
        self.import_member_cmts(member, mbr)
        self.update_counter(MEMBER)
        
        
    def import_member_cmts(self, member, mbr):
        """
        Processes REGULAR_CMT and REPEATABLE_CMT elements for members.
        
        Args:
            element: XML element object containing a REGULAR_CMT or
                REPEATABLE_CMT element
            mbr: Integer representing the member id
        """
        regcmt = member.find(REGULAR_CMT)
        if regcmt != None:
            idaapi.set_member_cmt(mbr, regcmt.text, False)
            self.update_counter(MEMBER + ':' + REGULAR_CMT)
        rptcmt = member.find(REPEATABLE_CMT)
        if rptcmt != None:
            idaapi.set_member_cmt(mbr, rptcmt.text, True)
            self.update_counter(MEMBER + ':' + REPEATABLE_CMT)
        

    def import_members(self, element, sptr):
        """
        Add data members to a structure.
        
        Args:
            element: STRUCTURE XML element containing MEMBER sub-elements.
            sptr:
        """
        members = element.findall(MEMBER)
        for member in members:
            self.import_member(member, sptr)
                

    def import_memory_contents(self, memory_contents, start, size):
        """
        Processes MEMORY_CONTENTS to load data for a memory block.
        
        Args:
            memory_contents: MEMORY_CONTENTS XML element.
        """
        if memory_contents.get(START_ADDR) == None:
            saddr = start
        else:
            saddr = self.get_address(memory_contents, START_ADDR)
        fname = self.get_attribute(memory_contents, FILE_NAME)
        offset = self.get_attribute_value(memory_contents, FILE_OFFSET)
        if memory_contents.get(LENGTH) == None:
            length = size
        else:
            length = self.get_attribute_value(memory_contents, LENGTH)
        #(binfilename, ext) = os.path.splitext(self.filename)
        #binfilename += ".bytes"
        (binfilename, fileext) = os.path.split(self.filename)
        binfilename += '/' + fname
        binfile = idaapi.loader_input_t()
        binfile.open(binfilename)
        binfile.file2base(offset,saddr,saddr+length,False)
        binfile.close()
        self.update_counter(MEMORY_CONTENTS)
    

    def import_memory_map(self, memory_map):
        """
        Processes the MEMORY_MAP element.
        
        Args:
            memory_map: MEMORY_MAP XML element.
            
        MEMORY_MAP is only processed by the IDA loader. It is ignored when
            run as an IDA plugin.
        """
        # import memory sections only when run as loader
        if self.plugin:
            return
        self.update_import(memory_map)
    

    def import_memory_reference(self, memory_reference):
        """
        Processes the MEMORY_REFERENCE element.
        
        Args:
            memory_reference: MEMORY_REFERENCE XML element.
        """
        if self.Options.MemoryReferences.checked == False:
            return        
        addr = self.get_address(memory_reference, ADDRESS)
        if self.has_attribute(memory_reference, OPERAND_INDEX):
            op = self.get_attribute_value(memory_reference, OPERAND_INDEX)
        if self.has_attribute(memory_reference, USER_DEFINED):
            user = self.get_attribute(memory_reference, USER_DEFINED)
        to_addr = self.get_address(memory_reference, TO_ADDRESS)
        if self.has_attribute(memory_reference, BASE_ADDRESS):
            base_addr = self.get_address(memory_reference, BASE_ADDRESS)
        if self.has_attribute(memory_reference, PRIMARY):
            primary = self.get_attribute(memory_reference, PRIMARY)
        self.update_counter(MEMORY_REFERENCE)
        # TODO: import_memory_reference - add code to store reference
 
 
    def import_memory_section(self, memory_section):
        """
        Creates a memory segment in the database.
        
        Args:
            memory_section: MEMORY_SECTION XML element.
            
        MEMORY_SECTION is only processed by the IDA loader. It is ignored
            when run as an IDA plugin.
        """
        # TODO: import_memory_section - handle overlays?
        # import memory sections only when run as loader
        if self.plugin:
            return
        name = self.get_attribute(memory_section, NAME)
        length = self.get_attribute_value(memory_section, LENGTH)

        s = idaapi.segment_t()
        addrstr = self.get_attribute(memory_section, START_ADDR)
        seg_str = ''
        if '::' in addrstr:
            # overlay - skip for now
            print '  ** Overlayed memory block %s skipped **  ' % name
            msg  = 'Overlayed memory block %s skipped!' % name
            msg += "\n\nXML Import does not currently support"
            msg += "\noverlayed memory blocks."
            idaapi.warning(msg)
            return
        elif ':' in addrstr:
            [seg_str, offset_str] = string.split(addrstr,':')
            offset = int(offset_str, 16)
            if self.is_int(seg_str):
                base = int(seg_str, 16)
                sel = idaapi.setup_selector(base)
                start = self.get_address(memory_section, START_ADDR)
            else:
                raise MultipleAddressSpacesNotSupported
                return
        else:
            sel = idaapi.allocate_selector(0)
            start = int(addrstr, 16)

        s.sel = sel
        s.startEA = start
        s.endEA = start+length
        s.bitness = self.addr_mode

        perms = ''
        if self.has_attribute(memory_section, PERMISSIONS):
            perms = self.get_attribute(memory_section, PERMISSIONS)
        s.perm = 0
        if 'r' in perms: s.perm |= idaapi.SEGPERM_READ
        if 'w' in perms: s.perm |= idaapi.SEGPERM_WRITE
        if 'x' in perms: s.perm |= idaapi.SEGPERM_EXEC
        idaapi.add_segm_ex(s, name, "", idaapi.ADDSEG_OR_DIE |
                                            idaapi.ADDSEG_QUIET)
        self.update_counter(MEMORY_SECTION)
        for memory_contents in memory_section.findall(MEMORY_CONTENTS):
            self.import_memory_contents(memory_contents, start, length)
    

    def import_processor(self, processor):
        """
        Processes the PROCESSOR element.
        
        Args:
            processor: PROCESSOR XML element.
        """
        name = self.get_attribute(processor, NAME)
        self.update_counter(PROCESSOR)
        if self.plugin:
            return
        address_model = self.get_attribute(processor, ADDRESS_MODEL)
        if address_model != None:
            flag = idaapi.ph_get_flag()
            if str.lower(address_model) == '16-bit':
                self.addr_mode = 0
            elif str.lower(address_model) == '32-bit':
                self.addr_mode = 1
            elif str.lower(address_model) == '64-bit':
                self.addr_mode = 2
        

    def import_program(self, program):
        """
        Processes the PROGRAM element.
        
        Args:
            program: PROGRAM XML element.
        """
        self.update_status(PROGRAM)
        name = self.get_attribute(program, NAME)
        if self.has_attribute(program, EXE_PATH):
            epath = self.get_attribute(program, EXE_PATH)
            idaapi.set_root_filename(epath)
        else:
            idaapi.set_root_filename(name)
        if self.has_attribute(program, EXE_FORMAT):
            eformat = self.get_attribute(program, EXE_FORMAT)
            RootNode = idaapi.netnode('Root Node')
            if IDA_SDK_VERSION < 650:
                RootNode.supset(1, eformat)
            else:
                RootNode.supset(idaapi.RIDX_FILE_FORMAT_NAME, eformat)
        if self.has_attribute(program, IMAGE_BASE):
            base = self.get_attribute_value(program, IMAGE_BASE)
            idaapi.set_imagebase(base)
        if self.has_attribute(program, INPUT_MD5):
            input_md5 = self.get_attribute(program, INPUT_MD5)
            # store original md5 in a special netnode
            md5 = idaapi.netnode(INPUT_MD5, len(INPUT_MD5), True)
            if (IDA_SDK_VERSION < 650):
                md5.supset(1302, input_md5)
            else:
                md5.supset(idaapi.RIDX_MD5, input_md5)
        self.update_counter(PROGRAM)
        """
        # TODO: this needs to be on "end" event for PROGRAM
        description = program.find(DESCRIPTION)
        if description != None:
            pass  # figure out what to do with it
        """

        
    def import_program_entry_point(self, program_entry_point):
        """
        Defines a program entry point.
        
        Args:
            program_entry_point: PROGRAM_ENTRY_POINT XML element.
                Contains the entry point address.
        """
        if self.Options.EntryPoints.checked == False:
            return
        addr = self.get_address(program_entry_point, ADDRESS)
        idaapi.add_entry(addr, addr, "", True)
        self.update_counter(PROGRAM_ENTRY_POINT)
    

    def import_register_value_range(self, register_value_range):
        """
        Defines the address range for a register value.
        
        Args:
            register_value_range: REGISTER_VALUE_RANGE XML element.
                Contains the register, value, start address and range length.
        """
        if self.Options.RegisterValues.checked == False:
            return
        self.update_counter(REGISTER_VALUE_RANGE)
        reg = self.get_attribute(register_value_range, REGISTER)
        if reg == 'cs': return
        value = self.get_attribute_value(register_value_range, VALUE)
        addr = self.get_address(register_value_range, START_ADDRESS)
        length = self.get_attribute_value(register_value_range, LENGTH)
        r = idaapi.str2reg(reg)
        if r >= idaapi.ph.regFirstSreg and r <= idaapi.ph.regLastSreg:
            if IDA_SDK_VERSION < 670:
                idaapi.splitSRarea1(addr, r, value, 2)
            else:
                idaapi.splitSRarea1(addr, r, value, idaapi.SR_user)
    

    def import_register_var(self, register_var, func):
        """
        Defines a register variable for a function.
        
        Args:
            register_var: REGISTER_VAR XML element.
                Contains register, variable name, and datatype.
            func: IDA function object
        """
        name = self.get_attribute(register_var, NAME)
        reg = self.get_attribute(register_var, REGISTER)
        if self.has_attribute(register_var, DATATYPE):
            datatype = self.get_attribute(register_var, DATATYPE)
        if self.has_attribute(register_var, DATATYPE_NAMESPACE):
            namespace = self.get_attribute(register_var, DATATYPE_NAMESPACE)
        idc.MakeLocal(func.startEA, func.endEA, reg, name)
        self.update_counter(REGISTER_VAR)
    

    def import_stack_frame(self, stack_frame, func):
        """
        Defines a stack frame for a function.
        
        Args:
            stack_frame: STACK_FRAME element with STACK_VAR child elements.
        """
        if self.has_attribute(stack_frame, LOCAL_VAR_SIZE):
            lvsize = self.get_attribute_value(stack_frame, LOCAL_VAR_SIZE)
        if self.has_attribute(stack_frame, PARAM_OFFSET):
            param_offset = self.get_attribute_value(stack_frame, PARAM_OFFSET)
        if self.has_attribute(stack_frame, REGISTER_SAVE_SIZE):
            reg_save_size = self.get_attribute_value(stack_frame,
                                                     REGISTER_SAVE_SIZE)
        if self.has_attribute(stack_frame, RETURN_ADDR_SIZE):
            retaddr_size = self.get_attribute_value(stack_frame,
                                                    RETURN_ADDR_SIZE)
        if self.has_attribute(stack_frame, BYTES_PURGED):
            bytes_purged = self.get_attribute_value(stack_frame, BYTES_PURGED)
        self.update_counter(STACK_FRAME)
        for stack_var in stack_frame.findall(STACK_VAR):
            self.import_stack_var(stack_var, func)
    

    def import_stack_reference(self, stack_reference):
        # import_stack_reference: NOT IMPLEMENTED
        #self.update_counter(STACK_REFERENCE)
        pass
                    

    def import_stack_var(self, stack_var, func):
        """
        Processes STACK_VAR element.
        
        Args:
            stack_var: STACK_VAR XML element.
            
        Stack variables are created by IDA's function analysis. 
        Only the STACK_VAR NAME attribute is used to set the name for
        a stack variable at the specified stack/frame offset. 
        """
        spoffset = self.get_attribute_value(stack_var, STACK_PTR_OFFSET)
        datatype = self.get_attribute(stack_var, DATATYPE)
        offset = spoffset + func.frsize + func.frregs
        if self.has_attribute(stack_var, FRAME_PTR_OFFSET):
            fpoffset = self.get_attribute_value(stack_var, FRAME_PTR_OFFSET)
            offset = fpoffset + func.frsize
        name = ''
        if self.has_attribute(stack_var, NAME):
            name = self.get_attribute(stack_var, NAME)
        if self.has_attribute(stack_var, DATATYPE_NAMESPACE):
            namespace = self.get_attribute(stack_var, DATATYPE_NAMESPACE)
        if self.has_attribute(stack_var, SIZE):
            size = self.get_attribute_value(stack_var, SIZE)
        self.update_counter(STACK_VAR)
        sf = idaapi.get_frame(func)
        if name != '':
            idaapi.set_member_name(sf, offset, name)
    

    def import_structure(self, structure):
        """
        Adds a structure.
        
        Args:
            structure: STRUCTURE XML element.
                Contains the STRUCTURE attributes and child elements.
        """
        if self.Options.DataTypes.checked == False:
            return
        name = self.get_attribute(structure, NAME)
        dtyp = idaapi.get_struc_id(name)
        if dtyp != idaapi.BADNODE:
            # duplicate name, try adding name space
            if self.has_attribute(structure, NAMESPACE) == False:
                return
            namespace = self.get_attribute(structure, NAMESPACE)
            name = namespace + '__' + name
            name.replace('/','_')
            name.replace('.','_')
            dtyp = idaapi.get_struc_id(name)
            # skip if still duplicate (could add sequence #)
            if dtyp != idaapi.BADNODE:
                return
        size = 0
        if self.has_attribute(structure, SIZE):
            size = self.get_attribute_value(structure, SIZE)
        if self.has_attribute(structure, VARIABLE_LENGTH):
            vl = self.get_attribute_value(structure, VARIABLE_LENGTH)
            isVariableLength = vl == 'y'
        sid = idaapi.add_struc(BADADDR, name, False)
        sptr = idaapi.get_struc(sid)
        self.update_counter(STRUCTURE)
        self.import_cmts(structure, sid, STRUCTURE)
        self.import_members(structure, sptr)
        t = idaapi.opinfo_t()
        if idaapi.get_struc_size(sptr) < size:
            idaapi.add_struc_member(sptr,"",size-1,idaapi.byteflag(),t,1)
        

    def import_symbol(self, symbol):
        """
        Adds a symbol name at the specified address.
        
        Args:
            symbol: SYMBOL XML element.
                Contains symbol name and address. Optionally includes
                type and mangled symbol.
        """
        if self.Options.Symbols.checked == False:
            return
        addr = self.get_address(symbol, ADDRESS)
        name = self.get_attribute(symbol, NAME)
        if self.has_attribute(symbol, MANGLED):
            name = self.get_attribute(symbol, MANGLED)
        flag = idaapi.SN_NOWARN
        if self.has_attribute(symbol, TYPE):
            typ = self.get_attribute(symbol, TYPE)
            if  typ == 'local': flag |= idaapi.SN_LOCAL
        idaapi.set_name(addr, name, flag)
        self.update_counter(SYMBOL)
    

    def import_typedef(self, type_def):
        # import_typedef: NOT IMPLEMENTED
        if self.Options.DataTypes.checked == False:
            return
        self.update_counter(TYPE_DEF)
        

    def import_union(self, union):
        """
        Adds a union datatype.
        
        Args:
            union: UNION XML element.
                Contains UNION attributes and child elements.
        """
        if self.Options.DataTypes.checked == False:
            return
        name = self.get_attribute(union, NAME)
        dtyp = idaapi.get_struc_id(name)
        if dtyp != idaapi.BADNODE:
            # duplicate name, try adding name space
            if self.has_attribute(union, NAMESPACE) == False:
                return
            namespace = self.get_attribute(union, NAMESPACE)
            name = namespace + '__' + name
            name.replace('/','_')
            name.replace('.','_')
            dtyp = idaapi.get_struc_id(name)
            # skip if still duplicate (could add sequence #)
            if dtyp != idaapi.BADNODE:
                return
        size = 0
        if self.has_attribute(union, SIZE):
            size = self.get_attribute_value(union, SIZE)
        sid = idaapi.add_struc(BADADDR, name, True)
        sptr = idaapi.get_struc(sid)
        self.update_counter(UNION)
        self.import_cmts(union, sid, UNION)
        self.import_members(union, sptr)
        t = idaapi.opinfo_t()
        if idaapi.get_struc_size(sptr) < size:
            idaapi.add_struc_member(sptr,"", size-1, idaapi.byteflag(), None, 1)
            

    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 update_counter(self, tag):
        """
        Updates the counter for the element tag.
        
        Args:
            tag: String representing element tag.
        """
        if tag in self.Elements:
            self.Counters[self.Elements[tag]] += 1
        else:
            self.Elements[tag] = len(self.Elements)
            self.Counters.append(1)
            self.Tags.append(tag)
    

    def update_import(self, element):
        """
        Update the element counter and processing status.
        
        Args:
            element: XML element
            
        This function is used to process certain high-level elements
        (such as COMMENTS, CODE_BLOCKS, SYMBOL_TABLE, FUNCTIONS, etc.)
        that are used to group sub-elements.
        """
        self.update_counter(element.tag)
        self.update_status(element.tag)
    

    def update_status(self, tag):
        """
        Displays the processing status in the IDA window.
        
        Args:
            tag: String representing XML element tag
        """
        status = 'Importing ' + tag
        idaapi.msg('\n%-35s' % status)
        idaapi.hide_wait_box()
        idaapi.show_wait_box(status)
    
        
# 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: zeropointdynamics/zelos
file: src/zelos/ext/plugins/overlay/zelos_ida.py
description: A comprehensive binary emulation and instrumentation platform.
wanted_name: ZelosOverlay
comment: Load an overlay file generated by Zelos.
created_at: 2020-03-02 17:22:51+00:00
pushed_at: 2023-01-31 00:27:41+00:00
url:
https://github.com/zeropointdynamics/zelos/blob/506554d20656c0d4c64c4d326baec179eede211a/src/zelos/ext/plugins/overlay/zelos_ida.py

content:
# Copyright (C) 2020 Zeropoint Dynamics

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
# ======================================================================

import ida_kernwin
import idaapi
import idc


class ApplyZelosOverlay(idaapi.action_handler_t):  # pragma: no cover
    """
    Applies the overlay from the user-selected memory snapshot.
    """

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

    def activate(self, ctx):
        import json

        filepath = ida_kernwin.ask_file(
            False, "*.zmu;*.overlay;*", "Load Zelos Overlay..."
        )
        if filepath is None:
            return
        f = open(filepath, "r")
        zelos_data = f.read()
        f.close()

        zelos_data = zelos_data[len("DISAS\n") :]
        zelos_dump = json.loads(zelos_data)

        # Apply the overlay data
        for comment in zelos_dump["comments"]:
            ea = comment["address"]
            try:
                comment_text = str(comment["text"])
            except UnicodeEncodeError:
                comment_text = ""
            color = comment.get("color", 0x73F0DF)

            # Set color of instruction line
            idaapi.set_item_color(ea, color)
            idaapi.set_cmt(ea, comment_text, False)

            # Set function name if not already changed
            idc.get_func_attr(ea, idc.FUNCATTR_START)
            name = idc.get_func_name(ea)
            if len(name) > 0 and name.startswith("zmu_") is False:
                idc.set_name(ea, "zmu_" + name)

        return 1

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


class zelosoverlay_t(idaapi.plugin_t):  # pragma: no cover
    """
    Adds a Zelos {View} menu option for loading an overlay.
    """

    flags = 0
    comment = "Load an overlay file generated by Zelos."
    help = comment
    wanted_name = "ZelosOverlay"
    wanted_hotkey = ""
    menu_name = "View/"
    menu_context = []

    def init(self):
        zelos_overlay_action = idaapi.action_desc_t(
            "zelosoverlay:action",
            "Load Zelos Overlay...",
            ApplyZelosOverlay(),
            "",
            "Load Zelos Overlay...",
            199,
        )

        idaapi.register_action(zelos_overlay_action)
        idaapi.attach_action_to_menu(
            "View/", "zelosoverlay:action", idaapi.SETMENU_APP
        )

        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    def run(self, arg):
        pass


def PLUGIN_ENTRY():  # pragma: no cover
    return zelosoverlay_t()


repository: techbliss/Ida_stylesheet_paster
file: Style_Paster_Plugin.py
description: Stylesheet Paster for ida pro 7.+
wanted_name: StyleSheet Paster
comment: Run me
created_at: 2017-12-14 21:38:55+00:00
pushed_at: 2017-12-14 21:41:45+00:00
url:
https://github.com/techbliss/Ida_stylesheet_paster/blob/64fc6b7cf79878ad0e35d699b9010a9c6ee940a1/Style_Paster_Plugin.py

content:
# Created by: Storm Shadow http://www.techbliss.org
import os
import ida_idaapi, ida_kernwin
import idc
from idc import *
from idaapi import *
import sys
sys.path.insert(0 , idaapi.idadir("plugins\\Style_Paster")) #change path to the icon
import ico
from ico import *

PLUGIN_VERSION = "1.0" #change Plugin Version
IDAVERISONS    = "IDA PRO 7.0+" #change Ida version
AUTHORS        = "Storm Shadow" #change author
DATE           = "2017" #change date
TWITTER        = "Twitter @zadow28" #change social media

def banner():
    banner_options = (PLUGIN_VERSION, AUTHORS, DATE, TWITTER, IDAVERISONS)
    banner_titles = "StyleSheet Paster v%s - (c) %s - %s - %s - %s" % banner_options #change Python editor

# print plugin banner
    print("---[" + banner_titles + "]---\n")

banner()

# 1) Create the handler class
class MyEditorHandlerPaste(idaapi.action_handler_t): # the class name must be unique for each plugin nb same as line (53)
    def __init__(self):
        idaapi.action_handler_t.__init__(self)

    # Run editor when invoked.
    def activate(self, ctx):
        g = globals()
        idahome = idaapi.idadir("plugins\\Style_Paster") #change to set plguin path where the main plguin script is
        IDAPython_ExecScript(idahome + "\\Style_Paste_main.py", g) #change for the main plugin script

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

class ripeyePaste(idaapi.plugin_t): #change class to unique name for each plugin , also change line (103)
    flags = idaapi.PLUGIN_FIX #different flags this is for plugin visible at startup
    comment = "Run me" #help me text
    help = "StyleSheet Paster" #help text
    wanted_name = "StyleSheet Paster" #change the plugins name
    wanted_hotkey = "" #the tooltip goes away in menu when setting it here ,DONT DO it! and only is shown in File/Plugins menu


    def editor_menuactionPaste(self): #change for something unique.also change line (90)
        action_desc = idaapi.action_desc_t(
            'my:editoractionPaste',  # The action name. This acts like an ID and must be unique same as line (64), (68)
            'StyleSheet Paster',  # The action text.
            MyEditorHandlerPaste(),  # The action handler must be unique , also change line (99)
            'Ctrl+P',  # Optional: the action shortcut DO IT  HERE!
            'Paste and test your custom styles',  # Optional: the action tooltip (available in menus/toolbar)
            idaapi.load_custom_icon(idaapi.idadir("plugins\\Style_Paster")+"\\iconss.png")  # hackish load action icon , if no custom icon use number from 1-150 from internal ida
        )

        # 3) Register the action
        idaapi.register_action(action_desc)

        idaapi.attach_action_to_menu(
            'File/StyleSheet Paster...',  # The relative path of where to add the action
            'my:editoractionPaste',  # The action ID (see line(51))
            idaapi.SETMENU_APP)  # We want to append the action after the 'Manual instruction...

        form = idaapi.get_current_tform()
        idaapi.attach_action_to_popup(form, None, "my:editoractionPaste", None) # The action ID (see line(51)

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """
        #self._icon_id_file = idaapi.BADADDR
        # attempt plugin initialization
        try:
            self._install_plugin()

        # failed to initialize or integrate the plugin, log and skip loading
        except Exception as e:
            form = idaapi.get_current_tform()
            pass

        return PLUGIN_KEEP

    def _install_plugin(self):
        """
        Initialize & integrate the plugin into IDA.
        """
        self.editor_menuactionPaste() #same as line (49)
        self._init()

    def term(self):
        pass

    def run(self, arg = 0):
        #we need the calls again if we wanna load it via File/Plugins/editor
        idaapi.msg("StyleSheet Paster Loaded to menu \n use Alt+P hot key to quick load ")
        hackish = MyEditorHandlerPaste() #must be the same as line (53) change also hackish =  must be unique also line (100)
        hackish.activate(self)  #hackish must the same as line hackish = (99)

def PLUGIN_ENTRY():
    return ripeyePaste() #must be unique for each plugin and same as line 43


repository: pfalcon/idapython
file: examples/ex_prefix_plugin.py
description: Automatically exported from code.google.com/p/idapython
wanted_name: user defined prefix
comment: This is a user defined prefix sample plugin
created_at: 2015-04-14 07:52:06+00:00
pushed_at: 2015-04-14 07:54:41+00:00
url:
https://github.com/pfalcon/idapython/blob/081b988a03b88867786ad4131269db6930637a5b/examples/ex_prefix_plugin.py

content:
import idaapi

PREFIX = idaapi.SCOLOR_INV + ' ' + idaapi.SCOLOR_INV

class prefix_plugin_t(idaapi.plugin_t):
    flags = 0
    comment = "This is a user defined prefix sample plugin"
    help = "This is help"
    wanted_name = "user defined prefix"
    wanted_hotkey = ""


    def user_prefix(self, ea, lnnum, indent, line, bufsize):
        #print("ea=%x lnnum=%d indent=%d line=%s bufsize=%d" % (ea, lnnum, indent, line, bufsize))

        if (ea % 2 == 0) and indent == -1:
            return PREFIX
        else:
            return ""


    def init(self):
        self.prefix_installed = idaapi.set_user_defined_prefix(8, self.user_prefix)
        if self.prefix_installed:
            print("prefix installed")

        return idaapi.PLUGIN_KEEP


    def run(self, arg):
        pass


    def term(self):
        if self.prefix_installed:
            idaapi.set_user_defined_prefix(0, None)
            print("prefix uninstalled!")


def PLUGIN_ENTRY():
    return prefix_plugin_t()



repository: RomanRybachek/Copy_RVA
file: rva_copy.py
description: Plugin for ida pro that copies RVA under cursor to clipboard.
url: https://github.com/RomanRybachek/Copy_RVA/blob/3ec6372d5ad7f7b4da06a72e300438a265d9ca3d/rva_copy.py
comment: Copy RVA under cursor
created_at: 2023-07-28 10:03:01+00:00
pushed_at: 2023-07-28 10:57:10+00:00
content:
import idaapi
import idc
import time

import PyQt5.QtGui as QtGui
import PyQt5.QtCore as QtCore
import PyQt5.QtWidgets as QtWidgets
from PyQt5.Qt import QApplication

PLUGIN_NAME = "Copy RVA"
PLUGIN_HOTKEY = ''

class rva_copy(idaapi.plugin_t):

    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
    comment = "Copy RVA under cursor"
    wanted_name = PLUGIN_NAME
    wanted_hotkey = PLUGIN_HOTKEY


    def init(self):

        self._init_action_copy_bytes()
        self._init_hooks()

        idaapi.msg("%s initialized...\n" % (self.wanted_name))
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name)


    def term(self):
        self._hooks.unhook()
        self._del_action_copy_bytes()

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

    def _init_hooks(self):
        self._hooks = Hooks()
        self._hooks.ready_to_run = self._init_hexrays_hooks
        self._hooks.hook()

    def _init_hexrays_hooks(self):
        if idaapi.init_hexrays_plugin():
            idaapi.install_hexrays_callback(self._hooks.hxe_callback)

    ACTION_COPY_RVA  = "prefix:copy_rva"

    def _init_action_copy_bytes(self):
        action_desc = idaapi.action_desc_t(
            self.ACTION_COPY_RVA,         
            "Copy RVA",                   
            IDACtxEntry(copy_rva),        
            PLUGIN_HOTKEY,
            "Copy RVA under cursor",
            31
        )
        assert idaapi.register_action(action_desc), "Action registration failed"


    def _del_action_copy_bytes(self):
        idaapi.unregister_action(self.ACTION_COPY_RVA)
        pass

def PLUGIN_ENTRY():
    return rva_copy()

def copy_rva():
    ea = idc.get_screen_ea()
    base = idaapi.get_imagebase()
    rva = hex(ea - base)
    
    QApplication.clipboard().setText(rva)
    print("RVA was copied:", rva)

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

def inject_hex_copy_actions(form, popup, form_type):

    if form_type == idaapi.BWN_DISASMS:

        idaapi.attach_action_to_popup(
            form,
            popup,
            rva_copy.ACTION_COPY_RVA,
            "Copy RVA",
            idaapi.SETMENU_APP
        )

    return 0

class Hooks(idaapi.UI_Hooks):

    def finish_populating_widget_popup(self, widget, popup):
        inject_hex_copy_actions(widget, popup, idaapi.get_widget_type(widget))
        return 0

    def finish_populating_tform_popup(self, form, popup):
        inject_hex_copy_actions(form, popup, idaapi.get_tform_type(form))
        return 0

    def hxe_callback(self, event, *args):
        if event == idaapi.hxe_populating_popup:
            form, popup, vu = args

            idaapi.attach_action_to_popup(
                form,
                popup,
                rva_copy.ACTION_COPY_RVA,
                "Copy RVA",
                idaapi.SETMENU_APP,
            )

        return 0


repository: oacia/stalker_trace_so
file: stalker_trace_so.py
description: 一个IDA插件，利用frida-stalker在加载so时打印出所有函数调用，解决frida-trace无法在so加载时trace的问题
wanted_name: stalker trace so
comment: stalker trace so
created_at: 2023-11-17 10:35:33+00:00
pushed_at: 2024-12-10 15:00:14+00:00
url:
https://github.com/oacia/stalker_trace_so/blob/188ff08b01a30e3635143cd239ea038efde3cd25/stalker_trace_so.py

content:
# -*- coding:utf-8 -*-
import os
from idaapi import plugin_t
from idaapi import PLUGIN_PROC
from idaapi import PLUGIN_OK
from idaapi import get_imagebase
import idaapi
import ida_nalt
import idautils
import idc
import random
from functools import reduce

template_js = '''
var func_addr = [[func_addr]];
var func_name = [[func_name]];
var so_name = "[so_name]";

/*
    @param print_stack: Whether printing stack info, default is false.
*/
var print_stack = false;

/*
    @param print_stack_mode
    - FUZZY: print as much stack info as possible
    - ACCURATE: print stack info as accurately as possible
    - MANUAL: if printing the stack info in an error and causes exit, use this option to manually print the address
*/
var print_stack_mode = "FUZZY";

function addr_in_so(addr){
    var process_Obj_Module_Arr = Process.enumerateModules();
    for(var i = 0; i < process_Obj_Module_Arr.length; i++) {
        if(addr>process_Obj_Module_Arr[i].base && addr<process_Obj_Module_Arr[i].base.add(process_Obj_Module_Arr[i].size)){
            console.log(addr.toString(16),"is in",process_Obj_Module_Arr[i].name,"offset: 0x"+(addr-process_Obj_Module_Arr[i].base).toString(16));
        }
    }
}
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
        {
            onEnter: function (args) {
                var pathptr = args[0];
                if (pathptr !== undefined && pathptr != null) {
                    var path = ptr(pathptr).readCString();
                    //console.log(path);
                    if (path.indexOf(so_name) >= 0) {
                        this.is_can_hook = true;
                    }
                }
            },
            onLeave: function (retval) {
                if (this.is_can_hook) {
                    // note: you can do any thing before or after stalker trace so.

                    trace_so();
                }
            }
        }
    );
}

function trace_so(){
    var times = 1;
    var module = Process.getModuleByName(so_name);
    var pid = Process.getCurrentThreadId();
    console.log("start Stalker!");
    Stalker.exclude({
        "base": Process.getModuleByName("libc.so").base,
        "size": Process.getModuleByName("libc.so").size
    })
    Stalker.follow(pid,{
        events:{
            call:false,
            ret:false,
            exec:false,
            block:false,
            compile:false
        },
        onReceive:function(events){
        },
        transform: function (iterator) {
            var instruction = iterator.next();
            do{
                if (func_addr.indexOf(instruction.address - module.base) !== -1) {
                    console.log("call" + times + ":" + func_name[func_addr.indexOf(instruction.address - module.base)])
                    times = times + 1
                    if (print_stack) {
                        if (print_stack_mode === "FUZZY") {
                            iterator.putCallout((context) => {
                                console.log("backtrace:\\n"+Thread.backtrace(context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\\n'));
                                console.log('---------------------')
                            });
                        }
                        else if (print_stack_mode === "ACCURATE") {
                            iterator.putCallout((context) => {
                                console.log("backtrace:\\n"+Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n'));
                                console.log('---------------------')
                            })
                        }

                        else if (print_stack_mode === "MANUAL") {
                            iterator.putCallout((context) => {
                                console.log("backtrace:")
                                Thread.backtrace(context, Backtracer.FUZZY).map(addr_in_so);
                                console.log('---------------------')
                            })
                        }
                    }
                }
                iterator.keep();
            } while ((instruction = iterator.next()) !== null);
        },

        onCallSummary:function(summary){

        }
    });
    console.log("Stalker end!");
}

setImmediate(hook_dlopen,0);
        '''


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

    def finish_populating_widget_popup(self, form, popup):
        form_type = idaapi.get_widget_type(form)
        if form_type == idaapi.BWN_FUNCS or form_type == idaapi.BWN_PSEUDOCODE or form_type == idaapi.BWN_DISASM:
            idaapi.attach_action_to_popup(form, popup, "stalkerTraceSo:genJsScript", None)


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

    def activate(self, ctx):

        if ctx.widget_type == idaapi.BWN_FUNCS:
            selected = [idaapi.getn_func(idx).start_ea for idx in ctx.chooser_selection]
        else:
            selected = idautils.Functions()

        generate_js_script(selected)

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


def generate_hook_code(template_js, func_addr, func_name, so_name):
    replacements = {
        "[func_addr]": ', '.join(func_addr),
        "[func_name]": ', '.join(func_name),
        "[so_name]": "%s" % so_name
    }
    return reduce(lambda acc, item: acc.replace(item[0], item[1]), replacements.items(), template_js)


def generate_js_script(func_list):
    func_addr = []
    func_name = []
    so_base = get_imagebase()
    for func_ea in func_list:
        # thumb mode
        if idc.get_sreg(func_ea, "T"):
            func_addr.append(hex(func_ea + 1 - so_base))
        else:
            func_addr.append(hex(func_ea - so_base))
        func_name.append('"{}"'.format(idc.get_func_name(func_ea)))

    so_path, so_name = os.path.split(ida_nalt.get_input_file_path())
    hook_code = generate_hook_code(template_js, func_addr, func_name, so_name)
    r = [random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(5)]
    script_name = "trace_" + so_name.split(".")[0] + '_' + ''.join(r) + ".js"
    save_path = os.path.join(so_path, script_name)
    with open(save_path, "w", encoding="utf-8") as f:
        f.write(hook_code)

    print("usage:")
    print(f'frida -U -l "{save_path}" -f [package name]')


class stalker_trace_so(plugin_t):
    flags = PLUGIN_PROC
    comment = "stalker trace so"
    help = ""
    wanted_name = "stalker trace so"
    wanted_hotkey = ""

    def init(self):
        print("stalker_trace_so plugin has been loaded.")
        idaapi.register_action(
            idaapi.action_desc_t("stalkerTraceSo:genJsScript", "stalker trace so", GenerateFridaHookScript(), None,
                                 None, 201))
        # Add ui hook
        self.ui_hook = UI_Hook()
        self.ui_hook.hook()

        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        generate_js_script(idautils.Functions())

    def term(self):
        pass


def PLUGIN_ENTRY():
    return stalker_trace_so()


repository: gistable/gistable
file: all-gists/eeea2093888e8d4004ec06b5a1dede1e/snippet.py
description: GitHub gists and the dockerfiles needed to run them.
wanted_name: CallsHintsPlugin
comment: Display the calls and strings referenced by a function as hints.
created_at: 2018-02-01 16:19:24+00:00
pushed_at: 2023-02-11 18:20:10+00:00
url:
https://github.com/gistable/gistable/blob/665d39a2bd82543d5196555f0801ef8fd4a3ee48/all-gists/eeea2093888e8d4004ec06b5a1dede1e/snippet.py

content:
'''
IDA plugin to display the calls and strings referenced by a function as hints.

Installation: put this file in your %IDADIR%/plugins/ directory.
Author: Willi Ballenthin <william.ballenthin@fireeye.com>
Licence: Apache 2.0
'''
import idc
import idaapi
import idautils


###############################################################################
#### begin: idapython_lex_curline
# via: https://gist.github.com/williballenthin/466eb28679d30e212ffac57e4a9ceaa5
# note: inline here for simplicity


# inverse mapping of color value to name.
# ref: https://www.hex-rays.com/products/ida/support/sdkdoc/group___s_c_o_l_o_r__.html#ga6052470f86411b8b5ffdf4af4bbee225
INV_COLORS = {
    0x1: 'COLOR_DEFAULT',  #= 0x01,         // Default
    0x2: 'COLOR_REGCMT',   #= 0x02,         // Regular comment
    0x3: 'COLOR_RPTCMT',   #= 0x03,         // Repeatable comment (comment defined somewhere else)
    0x4: 'COLOR_AUTOCMT',  #= 0x04,         // Automatic comment
    0x5: 'COLOR_INSN',     #= 0x05,         // Instruction
    0x6: 'COLOR_DATNAME',  #= 0x06,         // Dummy Data Name
    0x7: 'COLOR_DNAME',    #= 0x07,         // Regular Data Name
    0x8: 'COLOR_DEMNAME',  #= 0x08,         // Demangled Name
    0x9: 'COLOR_SYMBOL',   #= 0x09,         // Punctuation
    0xa: 'COLOR_CHAR',     #= 0x0A,         // Char constant in instruction
    0xb: 'COLOR_STRING',   #= 0x0B,         // String constant in instruction
    0xc: 'COLOR_NUMBER',   #= 0x0C,         // Numeric constant in instruction
    0xd: 'COLOR_VOIDOP',   #= 0x0D,         // Void operand
    0xe: 'COLOR_CREF',     #= 0x0E,         // Code reference
    0xf: 'COLOR_DREF',     #= 0x0F,         // Data reference
    0x10: 'COLOR_CREFTAIL', #= 0x10,         // Code reference to tail byte
    0x11: 'COLOR_DREFTAIL', #= 0x11,         // Data reference to tail byte
    0x12: 'COLOR_ERROR',    #= 0x12,         // Error or problem
    0x13: 'COLOR_PREFIX',   #= 0x13,         // Line prefix
    0x14: 'COLOR_BINPREF',  #= 0x14,         // Binary line prefix bytes
    0x15: 'COLOR_EXTRA',    #= 0x15,         // Extra line
    0x16: 'COLOR_ALTOP',    #= 0x16,         // Alternative operand
    0x17: 'COLOR_HIDNAME',  #= 0x17,         // Hidden name
    0x18: 'COLOR_LIBNAME',  #= 0x18,         // Library function name
    0x19: 'COLOR_LOCNAME',  #= 0x19,         // Local variable name
    0x1A: 'COLOR_CODNAME',  #= 0x1A,         // Dummy code name
    0x1B: 'COLOR_ASMDIR',   #= 0x1B,         // Assembler directive
    0x1C: 'COLOR_MACRO',    #= 0x1C,         // Macro
    0x1D: 'COLOR_DSTR',     #= 0x1D,         // String constant in data directive
    0x1E: 'COLOR_DCHAR',    #= 0x1E,         // Char constant in data directive
    0x1F: 'COLOR_DNUM',     #= 0x1F,         // Numeric constant in data directive
    0x20: 'COLOR_KEYWORD',  #= 0x20,         // Keywords
    0x21: 'COLOR_REG',      #= 0x21,         // Register name
    0x22: 'COLOR_IMPNAME',  #= 0x22,         // Imported name
    0x23: 'COLOR_SEGNAME',  #= 0x23,         // Segment name
    0x24: 'COLOR_UNKNAME',  #= 0x24,         // Dummy unknown name
    0x25: 'COLOR_CNAME',    #= 0x25,         // Regular code name
    0x26: 'COLOR_UNAME',    #= 0x26,         // Regular unknown name
    0x27: 'COLOR_COLLAPSED',#= 0x27,         // Collapsed line

    #  // Fictive colors
    0x28: 'COLOR_ADDR',     #= 0x28, // hidden address marks
                            #        // The address is represented as 8digit
                            #        // hex number: 01234567
                            #        // It doesn't have COLOR_OFF pair
                            #        // NB: for 64-bit IDA, the address is 16digit

    0x29: 'COLOR_OPND1',    #= COLOR_ADDR+1, // Instruction operand 1
    0x2A: 'COLOR_OPND2',    #= COLOR_ADDR+2, // Instruction operand 2
    0x2B: 'COLOR_OPND3',    #= COLOR_ADDR+3, // Instruction operand 3
    0x2C: 'COLOR_OPND4',    #= COLOR_ADDR+4, // Instruction operand 4
    0x2D: 'COLOR_OPND5',    #= COLOR_ADDR+5, // Instruction operand 5
    0x2E: 'COLOR_OPND6',    #= COLOR_ADDR+6, // Instruction operand 6

    0x32: 'COLOR_UTF8',     #= COLOR_ADDR+10;// Following text is UTF-8 encoded
}


class Symbol(object):
    def __init__(self, type):
        super(Symbol, self).__init__()
        self.type = type

    def __str__(self):
        raise NotImplementedError()


class StringSymbol(Symbol):
    def __init__(self, string):
        super(StringSymbol, self).__init__('string')
        self.string = string

    def __str__(self):
        return 'STRING=' + self.string


class ColorOnSymbol(Symbol):
    def __init__(self, color):
        super(ColorOnSymbol, self).__init__('coloron')
        self.color = ord(color)

    def __str__(self):
        return 'COLORON=' + INV_COLORS[self.color]


class ColorOffSymbol(Symbol):
    def __init__(self, color):
        super(ColorOffSymbol, self).__init__('coloroff')
        self.color = ord(color)

    def __str__(self):
        return 'COLOROFF=' + INV_COLORS[self.color]


class ColorInvSymbol(Symbol):
    def __init__(self):
        super(ColorInvSymbol, self).__init__('colorinv')

    def __str__(self):
        return 'COLORINV'



def lex(curline):
    '''
    split the line returned by `get_custom_viewer_curline` into symbols.
    it pulls out the strings, color directives, and escaped characters.
    
    Args:
      curline (str): a line returned by `idaapi.get_custom_viewer_curline`
    
    Returns:
      generator: generator of Symbol subclass instances
    '''

    offset = 0
    cur_word = []
    while offset < len(curline):

        c = curline[offset]

        if c == idaapi.COLOR_ON:
            if cur_word:
                yield StringSymbol(''.join(cur_word))
                cur_word = []

            offset += 1
            color = curline[offset]

            yield ColorOnSymbol(color)
            offset += 1

        elif c == idaapi.COLOR_OFF:
            if cur_word:
                yield StringSymbol(''.join(cur_word))
                cur_word = []

            offset += 1
            color = curline[offset]

            yield ColorOffSymbol(color)
            offset += 1

        elif c == idaapi.COLOR_ESC:
            if cur_word:
                yield StringSymbol(''.join(cur_word))
                cur_word = []

            offset += 1
            c = curline[offset]

            cur_word.append(c)
            offset += 1

        elif c == idaapi.COLOR_INV:
            if cur_word:
                yield StringSymbol(''.join(cur_word))
                cur_word = []

            yield ColorInvSymbol()
            offset += 1

        else:
            cur_word.append(c)
            offset += 1


def get_color_at_char(curline, index):
    curlen = 0
    curcolor = 0
    for sym in lex(curline):
        if sym.type == 'string':
            curlen += len(sym.string)
            if curlen >= index:
                return curcolor
        elif sym.type == 'coloron':
            curcolor = sym.color
        elif sym.type == 'coloroff':
            curcolor = 0
        else:
            curcolor = 0

    return curcolor


def get_token_at_char(curline, index):
    curlen = 0
    curcolor = 0
    for sym in lex(curline):
        if sym.type == 'string':
            curlen += len(sym.string)
            if curlen >= index:
                return sym.string
        else:
            continue

    return ''


#### end: idapython_lex_curline
###############################################################################



def enum_function_addrs(fva):
    '''
    yield the effective addresses of each instruction in the given function.
    these addresses are not guaranteed to be in any order.
    
    Args:
      fva (int): the starting address of a function
    
    Returns:
      sequence[int]: the addresses of each instruction
    '''
    f = idaapi.get_func(fva)
    if not f:
        raise ValueError('not a function')

    for block in idaapi.FlowChart(f):
        ea = block.startEA
        while ea <= block.endEA:
            yield ea
            ea = idc.NextHead(ea)


def enum_calls_in_function(fva):
    '''
    yield the call instructions in the given function.
    
    Args:
      fva (int): the starting address of a function
    
    Returns:
      sequence[tuple[int, str]]: the address of a call instruction, and the disassembly line at that address
    '''
    for ea in enum_function_addrs(fva):
        if idc.GetMnem(ea) != 'call':
            continue

        disasm = idc.GetDisasm(ea)

        yield ea, disasm


def enum_string_refs_in_function(fva):
    '''
    yield the string references in the given function.
    
    Args:
      fva (int): the starting address of a function
    
    Returns:
      sequence[tuple[int, int, str]]: tuples of metadata, including:
       - the address of the instruction referencing a string
       - the address of the string
       - the string
    '''
    for ea in enum_function_addrs(fva):
        for ref in idautils.DataRefsFrom(ea):
            stype = idc.GetStringType(ref)
            if stype < 0 or stype > 7:
                continue

            CALC_MAX_LEN = -1
            s = str(idc.GetString(ref, CALC_MAX_LEN, stype))

            yield ea, ref, s


def uniq(s):
    '''
    yield the unique items in the given sequence.
    only provide the first copy encountered, skipping subsequent copies.
    
    Args:
      s (sequence[T]): a sequence of things

    Returns:
      sequence[T]: a sequence of the unique copies
    '''
    seen = set([])

    for item in s:
        if item in seen:
            continue

        yield item

        seen.add(item)


def render_function_hint(fva):
    '''
    create a textual report for the call and string references in the given function.
    
    eg.

        3 calls, 4 strings

        calls:
          - call     new
          - call     gethostbyname
          - call     delete
    
        strings:
          - www.google.com
          - www.bing.com
          - www.yahoo.com
          - www.cnn.com
    
    Args:
      fva (int): the starting address of a function

    Returns:
      str: the report
    '''
    ret = []

    # use `uniq` here (vs using a set) cause we want to maintain *some* semblance of order.
    calls = list(uniq(d for f, d in enum_calls_in_function(fva)))
    strings = list(uniq(s for o, r, s in enum_string_refs_in_function(fva)))

    # this would be a good place to use Jinja2 templating,
    #  but lets not require that external dependency
    ret.append('%d calls, %s strings' % (len(calls), len(strings)))

    ret.append('')
    ret.append('calls:')
    for call in calls:
        ret.append('  - ' + call)
    if not calls:
        ret.append('  (none)')

    ret.append('')
    ret.append('strings:')
    for string in strings:
        ret.append('  - ' + string)
    if not strings:
        ret.append('  (none)')

    return str('\n'.join(ret))


class CallsHintsHook(idaapi.UI_Hooks):
    def get_custom_viewer_hint(self, view, place):
        try:
            tform = idaapi.get_current_tform()
            if idaapi.get_tform_type(tform) != idaapi.BWN_DISASM:
                return None

            curline = idaapi.get_custom_viewer_curline(view, True)
            _, x, y = idaapi.get_custom_viewer_place(view, True)
            ea = place.toea()

            # "color" is a bit of misnomer: its the type of the symbol currently hinted
            color = get_color_at_char(curline, x)
            if color != idaapi.COLOR_ADDR:
                return None

            # for COLOR_ADDR tokens, we get something like:
            #   401000sub_401000
            # so we will need to prune the address from the start before we can use it :-(
            token = get_token_at_char(curline, x)

            # enumerate the operands of the instruction at this address
            # and search the token for the operand text
            func_name = None
            for i in range(3):
                o = idc.GetOpnd(ea, i)
                if not o:
                    break

                # if we have `offset sub_401000`, we want: `sub_401000`
                if ' ' in o:
                    o = o.partition(' ')[2]

                if o in token:
                    func_name = o
                    break

            if not func_name:
                return None

            # get the address given the function name
            fva = idc.LocByName(func_name)
            if not fva:
                return None

            # ensure its actually a function
            if not idaapi.get_func(fva):
                return None

            # this magic constant "1" is the number of "important lines" to display by default.
            # the remaining lines get shown if you scroll down while the hint is displayed, revealing more lines.
            return render_function_hint(fva), 1
        except Exception as e:
            print('CallsHintsPlugin: error: %s. Get in touch with @williballenthin.' % (str(e)))
            return None


class CallsHintsPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = 'Display the calls and strings referenced by a function as hints.'
    help = 'Display the calls and strings referenced by a function as hints.'
    wanted_name = "CallsHintsPlugin"
    wanted_hotkey = "Ctrl-'"

    def init(self):
        self.hooks = CallsHintsHook()
        return idaapi.PLUGIN_OK

    def run(self, arg):
        print('CallsHintsPlugin: run')
        self.hooks.hook()

    def term(self):
        print('CallsHintsPlugin: term')
        self.hooks.unhook()


def PLUGIN_ENTRY():
    return CallsHintsPlugin()


repository: andreafioraldi/IDAngr
file: idangr_plugin.py
description: Use angr in the IDA Pro debugger generating a state from the current debug session
wanted_name: IDAngr
created_at: 2018-02-06 19:12:37+00:00
pushed_at: 2020-07-22 15:30:00+00:00
url:
https://github.com/andreafioraldi/IDAngr/blob/dea91475893178b3a665b7be57eb4140ea002f84/idangr_plugin.py

content:
######################################################
# Author: Andrea Fioraldi <andreafioraldi@gmail.com> #
# License: BSD 2-Clause                              #
######################################################


class IDAngrPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = ""

    help = "IDAngr plugin: Use angr in the IDA Pro debugger generating a state from the current debug session"
    wanted_name = "IDAngr"
    wanted_hotkey = "Ctrl-Alt-I"
    

    def init(self):
        idaapi.msg("\n########### IDAngr plugin ###########\n")
        r = idaapi.attach_action_to_menu('View/Open subviews/', 'IDAngr Panel', idaapi.SETMENU_APP)
        if r is None:
            idaapi.msg("IDAngr plugin: add menu failed!\n")
        idaapi.msg("IDAngr plugin: shortcut key is Ctrl-Alt-I\n\n")
        return idaapi.PLUGIN_KEEP

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

    def term(self):
        idaapi.msg("IDAngr plugin: terminated\n")

    def openPanel(self):
        import idangr
        import idangr.gui
        idangr.gui.show()


def PLUGIN_ENTRY():
    return IDAngrPlugin()


repository: Finch4/Malware-Analysis-Reports
file: DeGobfuscate/IDA_DeGobfuscate_v3.py
description: My Malware Analysis Reports
wanted_name: DeGobfuscate
comment: Decrypts gobfuscate xored strings
created_at: 2021-01-03 15:32:05+00:00
pushed_at: 2022-05-24 13:30:18+00:00
url:
https://github.com/Finch4/Malware-Analysis-Reports/blob/4f3baae07575e799db97ec22cb271d89c0fb0879/DeGobfuscate/IDA_DeGobfuscate_v3.py

content:
#https://gist.github.com/alexander-hanel/8f0cd7b53d5e55f73de97de09a0eec8b

import idc
import idaapi
import idautils
import re
import ida_hexrays
import ida_kernwin
import ida_bytes
from idaapi import PluginForm
import ida_netnode
import struct
import binascii
from PyQt5 import QtCore, QtGui, QtWidgets, Qt
import sip
class degobfuscate(idaapi.plugin_t):
    flags = idaapi.PLUGIN_UNL
    comment = "Decrypts gobfuscate xored strings"
    help="https://github.com/Finch4/Malware-Analysis-Reports"
    wanted_name = "DeGobfuscate"
    wanted_hotkey = "Alt-F8"
    
    
    def init(self):
        self.plg = MyPluginFormClass()
        self.plg.Show("DeGobfuscate Strings")
        idaapi.msg("init() called!\n")
        return idaapi.PLUGIN_OK
    
    def to_decimal(self, buffer):
        list_1 = []
        #print(f"Buffer: {buffer}")
        for string in buffer:
            for i in range(0, len(string), 2):
                list_1.append(int(string[i:i+2], 16))
        return list_1

    def run(self, arg):
        for function in idautils.Functions():
            values = {}
            #function = ida_kernwin.get_screen_ea()
            function_start = idc.get_func_attr(function, idc.FUNCATTR_START)
            function_end = idc.get_func_attr(function, idc.FUNCATTR_END)
            function_name = idc.get_func_name(function)
            if re.search("func\d+", function_name):
                try:
                    decompiled_code = str(ida_hexrays.decompile(function))
                    lenght = re.search(r"i < (\w+); \+\+i", decompiled_code)
                    
                    if lenght != None:
                        # Getting hex values displayed in the decompiled code
                        values[function_name] = []
                        for z in re.findall(" = 0[xX]([0-9a-fA-F]+)", decompiled_code):
                                    swapped_list = []
                                    for i in range(0, len(z), 2):
                                        swapped_list.insert(0, z[i:i+2])
                                    z = "".join(map(str, swapped_list))
                                    #print(f"\n{function_name}: {z}")
                                    values[function_name].append(z)
                                    #print(f"Appending: {z} {function_name}")
                        
                        
                        
                        # Getting values from xmmword and unk_ chunks
                        xmmwords = re.findall(r"xmmword_(\w+)", decompiled_code)
                        
                        for xmmword in xmmwords:
                            #print(f"\nxmmword_{xmmword} -> {int(xmmword, 16)}")
                            
                            ida_bytes.create_data(int(xmmword, 16), ida_bytes.byte_flag(), 1, ida_netnode.BADNODE) # create byte at the specified address
                            data = binascii.hexlify(idaapi.get_bytes(int(xmmword, 16), int(lenght.group(1), 16))).decode("windows-1252")
                            values[function_name].append(data)
                            #print(binascii.hexlify(idaapi.get_bytes(int(xmmword, 16), int(lenght, 16)))) # get bytes
                            
                        unks_data = re.findall(r"qmemcpy.*&unk_(.*),", decompiled_code)
                        if unks_data != []:
                            values[function_name] = []
                            for unk in unks_data:
                                unk_data = data = binascii.hexlify(idaapi.get_bytes(int(unk, 16), int(lenght.group(1), 16))).decode("windows-1252")
                                values[function_name].append(unk_data)
                                #print(f"""\n{function_name}
                                #unk_{unk}: {unk_data}
                                #""")
                                #print(f"Appending: {unk_data} {function_name}")
                                   
                                   
                except idaapi.DecompilationFailure:
                    pass
                
                try:
                    lenght = int(len(values[function_name]) / 2)
                    #print(function_name)
                    string = self.to_decimal(values[function_name][0:lenght])
                    key = self.to_decimal(values[function_name][lenght::])
                    # print(f"""
                    # String: {string}
                    # Key: {key}
                    # Lenght: {lenght}
                    # """)
                    str_decrypted = ""
                    for i in range(0, len(key)):
                        str_decrypted += chr(string[i % len(string)] ^ key[i % len(key)])
                        #idaapi.msg(f"\n{hex(string[i % len(string)])} ^ {hex(key[i % len(key)])} {chr(string[i % len(string)] ^ key[i % len(key)])}\n")
                    if str_decrypted != "":    
                         #idaapi.msg("Decrypted: "+str_decrypted+"\n")
                         idc.set_func_cmt(function_start, str_decrypted, 0)
                         rowPosition = self.plg.table.rowCount()
                         self.plg.table.insertRow(rowPosition)
                         self.plg.table.setItem(rowPosition, 0, QtWidgets.QTableWidgetItem(function_name.replace(".","_")))
                         self.plg.table.setItem(rowPosition, 1, QtWidgets.QTableWidgetItem(str_decrypted))
                except:
                    pass
            
            
        
        

    def term(self):
        idaapi.msg("term() called!\n")
        
def PLUGIN_ENTRY():
    return degobfuscate()   
    


class MyPluginFormClass(PluginForm):
    def OnCreate(self, form):
        """
        Called when the widget is created
        """

        # Get parent widget
        self.parent = self.FormToPyQtWidget(form)
        self.PopulateForm()


    def PopulateForm(self):
        # Create layout
        layout = QtWidgets.QVBoxLayout()

        # table 
        self.table = QtWidgets.QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["Function", "String"])
            
      
        layout.addWidget(self.table)

        # make our created layout the dialogs layout
        self.parent.setLayout(layout)

      
        


    def OnClose(self, form):
        """
        Called when the widget is closed
        """
        pass

    


repository: lucamassarelli/yarasafe
file: ida-pro-plugin/SAFE_plugin.py
description: SAFE embeddings to match functions in yara
created_at: 2019-04-01 12:55:10+00:00
pushed_at: 2020-02-25 13:49:46+00:00
url:
https://github.com/lucamassarelli/yarasafe/blob/706bd4dc7b7ec3bf4e278f1650246799be57848d/ida-pro-plugin/SAFE_plugin.py

content:
import idautils
import logging
import idaapi
import idc
import os
import binascii
import hashlib
import capstone
import requests
import numpy as np
import json


class Config(idaapi.action_handler_t):
    PLUGIN_NAME = "SAFE"
    PLUGIN_COMMENT = "CREATE SAFE SIGNATURE FOR A FUNCTIONS"
    PLUGIN_HELP = ""
    PLUGIN_HOTKEY = "Shift-S"
    CONFIG_FILE_PATH = os.path.join(idc.GetIdaDirectory(), "cfg/SAFE.cfg")
    SERVING_URL = "http://yarasafe.diag.uniroma1.it:8500/v1/models/safe:predict"
    PLUGIN_TEST = False
    ACTION_NAME = "SAFE:ConfigAction"

    @staticmethod
    def init():

        NO_HOTKEY = ""
        SETMENU_INS = 0
        NO_ARGS = tuple()

        idaapi.register_action(idaapi.action_desc_t(Config.ACTION_NAME, "{} Config".format(Config.PLUGIN_NAME), Config()))
        idaapi.attach_action_to_menu("Options/", Config.ACTION_NAME, idaapi.SETMENU_APP)
        Config.load()

    @staticmethod
    def destory():

        idaapi.unregister_action(Config.ACTION_NAME)

        try:
            Config.save()
        except IOError:
            logging.warning("Failed to write config file")

    @staticmethod
    def load():
        try:
            maxlvl = int(open(Config.CONFIG_FILE_PATH, "rb").read())
            Config.SERVING_URL = maxlvl
        except:
            pass

    @staticmethod
    def save():
        config_data = str(Config.SERVING_URL)
        open(Config.CONFIG_FILE_PATH, "wb").write(config_data)

    @staticmethod
    def safe_config():
        input = idc.AskStr(str(Config.SERVING_URL), "Please enter the url for the tensorflow serving instance:")
        print("New Serving URL: " + input)
        Config.SERVING_URL = input

    def activate(self, ctx):
        self.safe_config()

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

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

class InstructionsConverter:

    def __init__(self, json_i2id):
        f = open(json_i2id, 'r')
        self.i2id = json.load(f)
        f.close()

    def convert_to_ids(self, instructions_list):
        ret_array = []
        # For each instruction we add +1 to its ID because the first
        # element of the embedding matrix is zero
        for x in instructions_list:
            if x in self.i2id:
                ret_array.append(self.i2id[x] + 1)
            elif 'X_' in x:
                # print(str(x) + " is not a known x86 instruction")
                ret_array.append(self.i2id['X_UNK'] + 1)
            elif 'A_' in x:
                # print(str(x) + " is not a known arm instruction")
                ret_array.append(self.i2id['A_UNK'] + 1)
            else:
                # print("There is a problem " + str(x) + " does not appear to be an asm or arm instruction")
                ret_array.append(self.i2id['X_UNK'] + 1)
        return ret_array

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

class FunctionNormalizer:

    def __init__(self, max_instruction):
        self.max_instructions = max_instruction

    def normalize(self, f):
        f = np.asarray(f[0:self.max_instructions])
        length = f.shape[0]
        if f.shape[0] < self.max_instructions:
            f = np.pad(f, (0, self.max_instructions - f.shape[0]), mode='constant')
        return f, length

    def normalize_function_pairs(self, pairs):
        lengths = []
        new_pairs = []
        for x in pairs:
            f0, len0 = self.normalize(x[0])
            f1, len1 = self.normalize(x[1])
            lengths.append((len0, len1))
            new_pairs.append((f0, f1))
        return new_pairs, lengths

    def normalize_functions(self, functions):
        lengths = []
        new_functions = []
        for f in functions:
            f, length = self.normalize(f)
            lengths.append(length)
            new_functions.append(f.tolist())
        return new_functions, lengths

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


def disassemble_func(address):
    func_dis = {}
    symbolic_calls = {}
    inst_num = 0
    flags = idc.get_func_flags(address)
    last_addr = address
    asm = ''
    cnt = 0
    for addr in FuncItems(address):
        cnt += 1
        ins = idautils.DecodeInstruction(addr)
        # print('decoded')
        byte_instr = idc.get_bytes(addr, ins.size)
        asm = asm + str(binascii.hexlify(byte_instr))
        inst_num = inst_num + 1
        last_addr = addr
        if idc.print_insn_mnem(addr) in ["call"]:
            # print('Call:'+str(ins))
            call_address = idc.get_operand_value(addr, 0)
            # print(call_address)
            start_addr = idc.first_func_chunk(call_address)
            symbolic_calls[start_addr] = idc.get_func_name(call_address)

    func_dis['bytecode'] = asm
    func_dis['symbolic_calls'] = symbolic_calls
    func_dis['start_address'] = idc.first_func_chunk(address)
    func_dis['end_address'] = last_addr
    func_dis['segment_address'] = idc.get_segm_start(address)
    func_dis['segment_name'] = idc.SegName(address)
    func_dis['name'] = idc.get_func_name(address)
    func_dis['inst_numbers'] = inst_num
    # attenzione sta cosa ci da la roba riconosciuta con flirt.
    func_dis['library_flag'] = flags & idc.FUNC_LIB
    
    print("Function contains {} instructions".format(cnt))
    
    return func_dis


def constantIndependt_hash(function1):
    string = ""
    for ins1 in function1:
        capstone_ins1 = ins1
        string = string + "<" + str(capstone_ins1.mnemonic)
        for op in capstone_ins1.operands:
            if (op.type == 1):
                string = string + ";" + str(op.reg)
        string = string + ">"
    m = hashlib.sha256()
    m.update(string.encode('UTF-8'))
    return m.hexdigest()


def filter_memory_references(i, symbols, API):
    inst = "" + i.mnemonic
    for op in i.operands:
        if (op.type == 1):
            inst = inst + " " + i.reg_name(op.reg)
        elif (op.type == 2):
            imm = int(op.imm)
            symbol = 'liavetevistiliavetevistisullerivedelfiume...INANIINANI'
            if str(imm) in symbols:
                symbol = str(symbols[str(imm)])
            if inst == 'call' and symbol in API:
                inst = inst + " " + symbol
            elif (-int(5000) <= imm <= int(5000)):
                inst = inst + " " + str(hex(op.imm))
            else:
                inst = inst + " " + str('HIMM')
        elif (op.type == 3):
            mem = op.mem
            if (mem.base == 0):
                r = "[" + "MEM" + "]"
            else:
                r = '[' + str(i.reg_name(mem.base)) + "*" + str(mem.scale) + "+" + str(mem.disp) + ']'
            inst = inst + " " + r
        if (len(i.operands) > 1):
            inst = inst + ","
    if "," in inst:
        inst = inst[:-1]
    inst = inst.replace(" ", "_")
    return str(inst)


def filter_asm_and_return_instruction_list(address, asm, symbols, arch, mode, API):
    binary = binascii.unhexlify(asm)
    md = capstone.Cs(arch, mode)
    md.detail = True
    insns = []
    cap_insns = []
    cnt = 0;
    for i in md.disasm(binary, address):
        cnt += 1
        insns.append(filter_memory_references(i, symbols, API))
        cap_insns.append(i)
    print("Found {} instructions".format(cnt))
    return (constantIndependt_hash(cap_insns), insns)


def list_functions_to_disassembled(functions, arch, mode):
    ret = []
    for f in functions:
        # print((f,x))
        address = f['start_address']
        symbols = f['symbolic_calls']
        bytecode = f['bytecode']
        symbols_clean = {}
        # TODO fix this unclen and unholly ugliness
        for key, value in symbols.items():
            symbols_clean[key] = value.replace('.', '')
        # print(symbols_clean)
        insns = filter_asm_and_return_instruction_list(address, bytecode, symbols_clean, arch, mode, [])
        ret.append(insns)
    return ret

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


class SAFE_Plugin(idaapi.plugin_t):
    flags = 0
    comment = Config.PLUGIN_COMMENT
    help = Config.PLUGIN_HELP
    wanted_name = Config.PLUGIN_NAME
    wanted_hotkey = Config.PLUGIN_HOTKEY

    def __init__(self, *args, **kwargs):
        super(SAFE_Plugin, self).__init__(*args, **kwargs)
        self._chooser = None

    def init(self):
        Config.init()
        return idaapi.PLUGIN_KEEP

    def run(self, arg=0):
        try:
            addr = idaapi.get_screen_ea()
            # print("Addr: " + str(addr))

            fcn_name = idc.GetFunctionName(addr)
            print("----------------------------------------------------------------------------------------------------------")
            print("SAFE Signature For Function: " + fcn_name)

            info = idaapi.get_inf_structure()
            
            if info.procName == 'metapc':
                arch = capstone.CS_ARCH_X86
            
            if info.is_64bit():
                mode = capstone.CS_MODE_64
            elif info.is_32bit():
                mode = capstone.CS_MODE_32
                
            

            start = idc.get_func_attr(addr, idc.FUNCATTR_START)
            # print("Start: " + str(start))

            obj = disassemble_func(start)
            function = list_functions_to_disassembled([obj], arch, mode)[0]

            prepappend = 'X_'
            inst = [prepappend + x for x in function[1]]

            converter = InstructionsConverter(os.path.join(idc.GetIdaDirectory(), "plugins", "word2id.json"))
            normalizer = FunctionNormalizer(150)
            converted = converter.convert_to_ids(inst)
            instructions, lenghts = normalizer.normalize_functions([converted])
            payload = {"signature_name": "safe", "inputs": {"instruction": instructions, "lenghts": lenghts}}
            print(payload)
            r = requests.post(Config.SERVING_URL, data=json.dumps(payload))
            embeddings = json.loads(r.text)
            if "outputs" in embeddings:
                print(json.dumps(embeddings["outputs"][0]))
            else:
                raise ValueError("Something bad happened when computing embeddings")
        except Exception as e:
            logging.warning("exception", exc_info=True)
        return

    def term(self):
        Config.destory()


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


def PLUGIN_ENTRY():
    return SAFE_Plugin()


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


if Config.PLUGIN_TEST:
    print "{} - test".format(Config.PLUGIN_NAME)
    p = SAFE_Plugin()
    p.init()
    p.run()
    p.term()


repository: Goatman13/bextr-helper
file: bextr_cmt.py
description: Create comment for bextr opcode with easy to read operation
wanted_name: Comment bextr
comment: Simplify bextr instruction.
created_at: 2022-11-30 05:35:34+00:00
pushed_at: 2022-11-30 05:39:20+00:00
url:
https://github.com/Goatman13/bextr-helper/blob/360ce2129da8f8f1a94380e89ee8acaa02259758/bextr_cmt.py

content:
from ida_bytes import *
from idaapi import *
from idc import *
import idaapi
import ida_bytes
import idc
import binascii

def is_amd64_idb():

	if ida_idp.ph.id != ida_idp.PLFM_386:
		return False
	return ida_ida.cvar.inf.is_64bit()

def get_mask(val):

	result = 0
	while val != 0:
		result |= 1 << val
		val -= 1
		
	result >>= 1
	return result

def resolve(ea):

	mask = get_operand_value(ea, 2) # grab mask reg number
	old_ea = ea
	insn_counter = 0
	while insn_counter < 30: # unsafe value, lower can't catch all... :/
		insn_counter += 1
		ea = prev_head(ea, 0)
		if print_insn_mnem(ea) == "mov" and get_operand_type(ea, 1) == 5 and get_operand_value(ea, 0) == mask: # mov x,imm
			if insn_counter > 15:
				print("WARNING! Mov instruction is more than 15 opcodes before bextr!")
				print("Result can be inaccurate.")
			value = get_operand_value(ea, 1)
			bits  = ((value >> 8) & 0xFF)
			if bits == 0:
				print("EE RR RR OO RR")
				return
			start = value & 0xFF
			bitmask = get_mask(bits)
			is_s = ""
			mask_mode = 1
			if mask_mode == 0:
				if bits > 1:
					is_s = "s"			
				string = "extract {:d} bit" + is_s + ", starting from bit {:d}"
				set_cmt(old_ea, string.format(bits, start), 0)
			else:
				source = print_operand(old_ea, 1)
				dest   = print_operand(old_ea, 0)
				#string = dest + " = " + source + " >> {:d} & 0x{:X}"
				string = source + " >> {:d} & 0x{:X}"
				set_cmt(old_ea, string.format(start, bitmask), 0)
			break
			
	ea = old_ea + 5
	return ea

def single_bextr():

	ea = get_screen_ea()
	opcode = print_insn_mnem(ea)
	if opcode != "bextr":
		print("Selected opcode is not bextr!")
		return

	resolve(ea)
	

def multi_bextr():
	
	ea  = get_first_seg()
	end = get_segm_end(ea) - 0x10

	while ea < BADADDR:
		print(hex(ea))
		if ea >= end:
			if get_next_seg(ea) != get_segm_end(ea): # if next seg is not right after that one
				ea = get_next_seg(ea)
				end = get_segm_end(ea) - 0x10
					
		if idaapi.getseg(ea).perm & idaapi.SEGPERM_EXEC == 0:
			ea = get_next_seg(ea)
			end = get_segm_end(ea) - 0x10
			
		if print_insn_mnem(ea) != "bextr":
			ea += 1
			continue
		
		ea = resolve(ea)

def start_plg():

	#multi_bextr()
	single_bextr()

class bextr_helper_t(idaapi.plugin_t):
	flags = idaapi.PLUGIN_UNL
	comment = "Simplify bextr instruction."
	help = ""
	wanted_name = "Comment bextr"
	wanted_hotkey = "F10"

	def init(self):
		if not is_amd64_idb():
			return ida_idaapi.PLUGIN_SKIP

		return idaapi.PLUGIN_KEEP
	
	def run(self, arg):
		start_plg()
	
	def term(self):
		pass

def PLUGIN_ENTRY():
	return bextr_helper_t()


repository: RolfRolles/FinSpyVM
file: FinSpyDeob.py
description: Static unpacker for FinSpy VM
url: https://github.com/RolfRolles/FinSpyVM/blob/b832f989c2d75613f7bcab53fa156021631e1c9c/FinSpyDeob.py
wanted_name: deobx86
comment: Deobfuscator
created_at: 2018-01-23 09:50:20+00:00
pushed_at: 2021-07-11 08:12:06+00:00
content:
import idaapi
import idc

input_md5 = '4A49135D2ECC07085A8B7C5925A36C0A'

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

	def ev_ana_insn(self, insn):
		b1 = idaapi.get_byte(insn.ea)
		if b1 >= 0x70 and b1 <= 0x7F:
			d1 = idaapi.get_byte(insn.ea+1)
			b2 = idaapi.get_byte(insn.ea+2)
			d2 = idaapi.get_byte(insn.ea+3)
			if b2 == b1 ^ 0x01 and d1-2 == d2:
				idaapi.put_byte(insn.ea, 0xEB)
				idaapi.put_word(insn.ea+2, 0x9090)
		
		elif b1 == 0x0F:
			b1_1 = idaapi.get_byte(insn.ea+1)
			d1   = idaapi.get_long(insn.ea+2)
			b2   = idaapi.get_byte(insn.ea+6)
			b2_1 = idaapi.get_byte(insn.ea+7)
			d2   = idaapi.get_long(insn.ea+8)
			if b2 == 0x0F and b1_1 ^ 0x01 == b2_1 and d1-6 == d2:
				idaapi.put_byte(insn.ea, 0xE9)
				idaapi.put_long(insn.ea+1, d1+1)
				idaapi.put_byte(insn.ea+5, 0x90)
				idaapi.put_word(insn.ea+6, 0x9090)
				idaapi.put_long(insn.ea+8, 0x90909090)
			
		return False

class deobx86_t(idaapi.plugin_t):
	flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
	comment = "Deobfuscator"
	wanted_hotkey = ""
	help = "Runs transparently"
	wanted_name = "deobx86"
	hook = None

	def init(self):
		self.hook = None
		if idc.GetInputMD5() != input_md5 or idaapi.ph_get_id() != idaapi.PLFM_386:
			return idaapi.PLUGIN_SKIP
				
		self.hook = deobX86Hook()
		self.hook.hook()
		return idaapi.PLUGIN_KEEP

	def run(self, arg):
		pass

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

def PLUGIN_ENTRY():
	return deobx86_t()


repository: NyaMisty/ida_gdb_auto_memory
file: gdb_auto_memory.py
description: Automatically setup memory region for IDA Remote GDB Debugger (gdbserver).
created_at: 2024-09-22 02:55:33+00:00
pushed_at: 2024-09-25 13:04:23+00:00
url:
https://github.com/NyaMisty/ida_gdb_auto_memory/blob/170100fc01955cbb7c97e7dc771933366628c9fe/gdb_auto_memory.py

content:
import sys
import re
import dataclasses

import idaapi
import idc

def debugp(*args, **kwargs):
    print('[gdb_auto_memory]', *args, **kwargs)

class GDB(object):

    def sendRaw(self, cmd):
        #debugp('Sending GDB: %s' % cmd)
        return idc.send_dbg_command('`' + cmd)
    
    def getPid(self):
        ret = self.sendRaw('?')
        m = re.findall('thread:([0-9a-f]+);', ret)
        if not m:
            debugp('invalid ret for "?" from GDB: %s' % repr(ret))
        pid = int(m[0], 16)
        return pid

    def _raw_qXfer(self, cmd): 
        r = self.sendRaw(cmd)
        if r[0] == 'l':
            return False, r[1:]
        elif r[0] == 'm':
            return True, r[1:]
        else:
            raise Exception('Invalid qXfer ret: %s' % r)
    
    def qXfer(self, cmd_part, chunk_size=0x10):
        cur = 0
        ret = ''
        while True:
            has_more, data = self._raw_qXfer('%s%x,%x' % (cmd_part, cur, chunk_size))
            #debugp(repr(data))
            cur += chunk_size
            ret += data
            if not has_more:
                break
        return ret
    
    def raw_qXfer(self, cmd): 
        r = self.sendGdbRaw(cmd)
        if r[0] == 'l':
            return False, r[1:]
        elif r[0] == 'm':
            return True, r[1:]
        else:
            raise Exception('invalid qXfer ret: %s' % r)
    
    def _raw_vFile(self, cmd):
        r = self.sendRaw(cmd)
        if r[0] != 'F':
            raise Exception('invalid vFile ret: %s' % r)
        body = r[1:]
        m = re.match('^([0-9a-f\-]+)(?:,([0-9a-f\-]+)|)(?:;([\S\s]*?)|)$', body)
        if not m:
            raise Exception('invalid vFile ret: %s' % r)
        _result, _errno, _attachment = m.groups()
        result = int(_result, 16)
        errno = int(_errno, 16) if _errno is not None else None
        attachment = _attachment
        return result, errno, attachment

    def vFileOpen(self, path, flag, mode):
        result, _, _ = self._raw_vFile("vFile:setfs:0")
        assert result == 0
        fd, errno, _ = self._raw_vFile("vFile:open:%s,%x,%x" % (path.encode().hex(), flag, mode))
        if fd < 0:
            raise Exception('cannot file %s: server returned %d errno %d' % (path, fd, errno))
        return fd
    
    def vFileRead(self, fd, count, offset):
        readCount, errno, data = self._raw_vFile("vFile:pread:%x,%x,%x" % (fd, count, offset))
        return readCount, errno, data

    def vFileReadAll(self, path, chunk_size=0x10):
        fd = self.vFileOpen(path, 0, 0o666)
        off = 0
        ret = ''
        while True:
            readCount, errno, data = self.vFileRead(fd, chunk_size, off)
            if readCount < 0:
                raise Exception('error during vFile read, errno %d' % errno)
            off += readCount
            ret += data
            if readCount < chunk_size:
                break
        return ret

#threads = qXfer('qXfer:threads:read::')

def pidmaps(mapsData):
    MAPS_LINE_RE = re.compile(r"""
        ^(?P<addr_start>[0-9a-f]+)-(?P<addr_end>[0-9a-f]+)\s+  # Address
        (?P<perms>\S+)\s+                                     # Permissions
        (?P<offset>[0-9a-f]+)\s+                              # Map offset
        (?P<dev>\S+)\s+                                       # Device node
        (?P<inode>\d+)                                        # Inode
        (?:\s+(?P<pathname>.*)|)$                               # Pathname
    """, re.VERBOSE)
    ret = {}
    for l in mapsData.splitlines():
        m = MAPS_LINE_RE.match(l)
        if not m:
            debugp('Skipping maps line: %s' % l)
            continue
        addr_start, addr_end, perms, offset, dev, inode, pathname = m.groups()
        ret[int(addr_start, 16)] = {
            'start': int(addr_start, 16),
            'end': int(addr_end, 16),
            'mode': perms,
            'offset': int(offset, 16),
            'dev': dev,
            'inode': inode,
            'name': pathname,
        }
    return ret

@dataclasses.dataclass(frozen=True)
class Region:
    start: int
    end: int
    name: str
    mode: str # rwxp

def buildRegionInfo(r):
    return Region(name=r['name'], start=r['start'], end=r['end'], mode=r['mode'])

def getMemInfo(mapsData):
    maps = pidmaps(mapsData)
    debugp('Got maps:', maps)
    modMaps = {}
    for _, m in maps.items():
        name = m['name']
        if name not in modMaps:
            modMaps[name] = []
        modMaps[name].append(m)
    
    for modName, regions in modMaps.items():
        rr = regions[0]
        rrinfo = buildRegionInfo(rr)
        if rr['inode'] and rr['inode'] != '0':
            # is module
            startEA = min(r['start'] for r in regions)
            endEA = max(r['end'] for r in regions)
            yield ('mod', dataclasses.replace(rrinfo, start=startEA, end=endEA))
        else:
            for r in regions:
                yield ('other', buildRegionInfo(r))

class GDBMemoryWatcher():
    def __init__(self):
        self._lastData = set()
        self.g = GDB()
        # debugp('Current pid:', self.pid)
    
    @property
    def pid(self):
        return self.g.getPid()
    
    @property
    def lastData(self):
        d = getattr(sys.modules['__main__'], 'GDBMemory_lastData', None)
        if not d:
            return set()
        if d[0] != self.pid:
            debugp('pid changed from %d to %d, ignoring cache' % (d[0], self.pid))
            return set()
        return d[1]
    @lastData.setter
    def lastData(self, data):
        setattr(sys.modules['__main__'], 'GDBMemory_lastData', (self.pid, data))

    def update(self):
        mapsData = self.g.vFileReadAll('/proc/%d/maps' % self.g.getPid())
        debugp('Current map:\n', mapsData)
        newData = set(repr(c) for c in getMemInfo(mapsData))
        #debugp(newData)

        # TODO: handle region unload
        diff = [eval(c) for c in newData - self.lastData]
        # debugp('Last: ', self.lastData)
        # debugp('New: ', newData)
        debugp('Diff: ', diff)
        self.lastData = newData

        def addDbgMod(name, base, size):
            ev = idaapi.debug_event_t()
            modinfo = ev.set_modinfo(idaapi.LIB_LOADED)
            modinfo.name = name
            modinfo.base = base
            modinfo.size = size
            modinfo.rebase_to = idaapi.BADADDR
            input_file_path = idaapi.dbg_get_input_path()
            if modinfo.name == input_file_path:
                modinfo.rebase_to = modinfo.base
            idaapi.handle_debug_event(ev, idaapi.RQ_SILENT|idaapi.RQ_SUSPEND)
        
        def addMemRegion(name, start, end, mode):
            m = idaapi.memory_info_t()
            m.start_ea = start
            m.end_ea = end
            m.sclass = 'DATA'
            m.name = name
            m.bitness = 2 # ///< Number of bits in segment addresses (0-16bit, 1-32bit, 2-64bit)
            m.perm = (idaapi.SEGPERM_READ if 'r' in mode else 0) | (idaapi.SEGPERM_WRITE if 'w' in mode else 0) | (idaapi.SEGPERM_EXEC if 'x' in mode else 0)
            return m
        
        # mm = idaapi.meminfo_vec_t()
        # # base memory
        # mm.push_back(addMemRegion('MEMORY', 0, idaapi.BADADDR - 1, 'rwxp'))
        # for typ, rr in diff:
        #     if typ == 'mod':
        #         addDbgMod(rr.name, rr.start, rr.end - rr.start)
        #         debugp('Adding module %s' % rr.name)
        #     else:
        #         mm.push_back(addMemRegion(rr.name, rr.start, rr.end, rr.mode))
        # idaapi.set_manual_regions(mm)
        for typ, rr in diff:
            if typ == 'mod':
                addDbgMod(rr.name, rr.start, rr.end - rr.start)
                debugp('Adding module %s' % rr.name)
            else:
                idaapi.add_segm(0, rr.start, rr.end, rr.name, 'UNK', idaapi.ADDSEG_NOAA)
        

class DbgHooks(idaapi.DBG_Hooks):
    def __init__(self, callback):
        super(DbgHooks, self).__init__()
        self.callback = callback

    def hook(self, *args):
        super(DbgHooks, self).hook(*args)

    def unhook(self, *args):
        super(DbgHooks, self).unhook(*args)

    def notify(self):
        self.callback()

    
    # def dbg_process_start(self, *args) -> None:
    #     self.notify()
    # # def dbg_process_exit(self, *args) -> None:
    # #     self.notify()
    # def dbg_process_attach(self, *args) -> None:
    #     self.notify()
    # # def dbg_process_detach(self, *args) -> None:
    # #     self.notify()
    # # def dbg_thread_start(self, *args) -> None:
    # #     self.notify()
    # # def dbg_thread_exit(self, *args) -> None:
    # #     self.notify()
    # def dbg_library_load(self, *args) -> None:
    #     self.notify()
    # def dbg_library_unload(self, *args) -> None:
    #     self.notify()
    # # def dbg_information(self, *args) -> None:
    # #     self.notify()
    # def dbg_exception(self, *args) -> int:
    #     self.notify()
    #     return -1
    # # def dbg_suspend_process(self, *args) -> None:
    # #     self.notify()
    # def dbg_bpt(self, *args) -> int:
    #     self.notify()
    #     return 0
    # # def dbg_trace(self, *args) -> int:
    # #     self.notify()
    # #     return 0
    # # def dbg_request_error(self, *args) -> None:
    # #     self.notify()
    # # def dbg_step_into(self, *args) -> None:
    # #     self.notify()
    # # def dbg_step_over(self, *args) -> None:
    # #     self.notify()
    # def dbg_run_to(self, *args) -> None:
    #     self.notify()
    # def dbg_step_until_ret(self, *args) -> None:
    #     self.notify()
    # # def dbg_bpt_changed(self, *args) -> None:
    # #     self.notify()
    # # def dbg_started_loading_bpts(self, *args) -> None:
    # #     self.notify()
    # # def dbg_finished_loading_bpts(self, *args) -> None:
    # #     self.notify()

    last_step_ea = None
    def dbg_suspend_process(self, *args):
        if len(args) > 0:
            event : idaapi.debug_event_t = args[0]
        else:
            event : idaapi.debug_event_t = idaapi.get_debug_event()
        # debugp('hooked called with %x at %x' % (event.eid(), event.ea))
        eid = event.eid()
        if eid & idaapi.STEP:
            self.last_step_ea = event.ea
        if eid & idaapi.BREAKPOINT:
            if self.last_step_ea is not None:
                if 0 <= abs(event.ea - self.last_step_ea) < 0x1000:
                    eid = idaapi.STEP # step breakpoint
        allowed_flags = idaapi.PROCESS_STARTED \
            | idaapi.BREAKPOINT \
            | idaapi.EXCEPTION \
            | idaapi.LIB_LOADED \
            | idaapi.LIB_UNLOADED \
            | idaapi.PROCESS_ATTACHED \
            | idaapi.PROCESS_SUSPENDED
        # we ignored STEP event
        if eid & allowed_flags:
            self.notify()

class IDACtxEntry(idaapi.action_handler_t):
    """
    A basic Context Menu class to utilize IDA's action handlers.
    """

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

    def activate(self, ctx):
        """
        Execute the embedded action_function when this context menu is invoked.
        """
        self.action_function()
        return 1

    def update(self, ctx):
        """
        Ensure the context menu is always available in IDA.
        """
        return idaapi.AST_ENABLE_ALWAYS

PLUGIN_NAME = 'gdb_auto_memory'
class gdb_auto_memory_plugin_t(idaapi.plugin_t):
    flags = 0
    comment = ""
    help = PLUGIN_NAME
    wanted_name = PLUGIN_NAME
    wanted_hotkey = "Ctrl-Alt-G"

    g_watcher = None
    g_curHook = None
    g_hooked = False
    g_uiHook = None
    
    def node(self):
        return idaapi.netnode("$ " + PLUGIN_NAME, 0, 1)
    
    def init(self):
        debugp("loading plugin...")
        self._init_actions()
        
        self.g_watcher = GDBMemoryWatcher()
        self.g_curHook = DbgHooks(self.g_watcher.update)
        if self.node().hashval_long("hook_enabled") == 1:
            debugp("automatically enabling hook")
            self.hook()
        
        class UIHooks(idaapi.UI_Hooks):
            def ready_to_run(self):
                """
                UI ready to run -- an IDA event fired when everything is spunup.

                NOTE: this is a placeholder func, it gets replaced on a live instance
                but we need it defined here for IDA 7.2+ to properly hook it.
                """
                pass

            def finish_populating_widget_popup(self, widget, popup):
                form_type = idaapi.get_widget_type(widget)
                if form_type == idaapi.BWN_MODULES:
                    idaapi.attach_action_to_popup(
                        widget,
                        popup,
                        gdb_auto_memory_plugin_t.ACTION_REFRESH,
                        "Refresh Module List",
                        idaapi.SETMENU_APP
                    )
                return 0
        self.g_uiHook = UIHooks()
        self.g_uiHook.hook()
        return idaapi.PLUGIN_KEEP

    def term(self):
        self.g_uiHook.unhook()
        self.g_curHook.unhook()
        idaapi.unregister_action(self.ACTION_REFRESH)
    
    def hook(self):
        debugp("enabling debugger hook")
        self.g_curHook.hook()
        self.g_hooked = True
    def unhook(self):
        debugp("disabling debugger hook")
        self.g_curHook.hook()
        self.g_hooked = False
        
    def run(self, arg=0):
        if not self.g_hooked:
            self.node().hashset_idx("hook_enabled", 1)
            self.hook()
        else:
            self.node().hashset_idx("hook_enabled", 0)
            self.unhook()

    ACTION_REFRESH = f"{PLUGIN_NAME}:refresh"
    def trigger(self):
        debugp("refreshing memory")
        self.g_watcher.update()
    
    def _init_actions(self):
        # describe the action
        action_desc = idaapi.action_desc_t(
            self.ACTION_REFRESH,                   # The action name.
            "Force Refresh GDB memory layout",             # The action text.
            IDACtxEntry(self.trigger),    # The action handler.
            'Ctrl-Alt-Shift-G',                      # Optional: action shortcut
            "Force Refresh GDB memory layout",       # Optional: tooltip
        )

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

def PLUGIN_ENTRY():
    return gdb_auto_memory_plugin_t()

if __name__ == '__main__':
    w = GDBMemoryWatcher()
    w.update()


repository: FoxAhead/IDA-Projects
file: Ascendancy/be_ornot_be.py
wanted_name: optimize x|~x
created_at: 2024-10-25 17:15:21+00:00
pushed_at: 2025-04-25 16:22:09+00:00
url:
https://github.com/FoxAhead/IDA-Projects/blob/71f85d20a5a20b0769be0e3026c6b4fd8000c37c/Ascendancy/be_ornot_be.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 installs a custom microcode optimization rule:
#        x | ~x => -1
#
#      To see this plugin in action please use be_ornot_be.idb
#

from ida_hexrays import *
import ida_idaapi

# recognize "x | ~x" and replace by -1
class subinsn_optimizer_t(minsn_visitor_t):
    cnt = 0
    def __init__(self):
        minsn_visitor_t.__init__(self)
    def visit_minsn(self):      # for each instruction...
        ins = self.curins       # take a reference to the current instruction
        # THE CORE OF THE PLUGIN IS HERE:
        # check the pattern "x | ~x"
        if ins.opcode == m_or and ins.r.is_insn(m_bnot) and ins.l == ins.r.d.l:
            if not ins.l.has_side_effects(): # avoid destroying side effects
                # pattern matched, convert to "mov -1, ..."
                ins.opcode = m_mov
                ins.l.make_number(-1, ins.r.size)
                ins.r = mop_t()
                self.cnt = self.cnt + 1 # number of changes we made
        return 0 # continue traversal

# a custom instruction optimizer, boilerplate code
class sample_optimizer_t(optinsn_t):
    def __init__(self):
        optinsn_t.__init__(self)
    def func(self, blk, ins, optflags):
        print("INSN: %s" % ins.dstr())
        opt = subinsn_optimizer_t()
        ins.for_all_insns(opt)
        if opt.cnt != 0:                # if we modified microcode,
            blk.mba.verify(True)        # run the verifier
        return opt.cnt                  # report the number of changes

# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "optimize x|~x"
    wanted_hotkey = ""
    comment = ""
    help = ""
    def init(self):
        if init_hexrays_plugin():
            self.optimizer = sample_optimizer_t()
            self.optimizer.install()
            print("Installed sample optimizer for 'x | ~x'")
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
            self.optimizer.remove()
    def run(self):
            pass

def PLUGIN_ENTRY():
    return my_plugin_t()


repository: BeaCox/ida-plugins
file: auto-enum/auto_enum.py
description: Some ida plugins i use (for ida9.0)
wanted_name: Auto Enum
comment: Automatically detect standard enums
created_at: 2024-09-20 03:25:27+00:00
pushed_at: 2024-09-20 06:07:05+00:00
url:
https://github.com/BeaCox/ida-plugins/blob/83c25f5ab8bd3df32e0445431ce552c756580f89/auto-enum/auto_enum.py

content:
import os.path
import logging
import re
import traceback
import functools
import string
import json

import idc
import idautils
import idaapi
import ida_typeinf
import ida_nalt
import ida_hexrays
import ida_funcs

from typing import Dict

# From https://github.com/tmr232/Sark/blob/main/sark/ui.py#L358
class ActionHandler(idaapi.action_handler_t):
    """A wrapper around `idaapi.action_handler_t`.

    The class simplifies the creation of UI actions in IDA >= 6.7.

    To create an action, simply create subclass and override the relevant fields
    and register it::

        class MyAction(ActionHandler):
            TEXT = "My Action"
            HOTKEY = "Alt+Z"

            def _activate(self, ctx):
                idaapi.msg("Activated!")

        MyAction.register()

    Additional Documentation:
        Introduction to `idaapi.action_handler_t`:
            http://www.hexblog.com/?p=886

        Return values for update (from the SDK):
            AST_ENABLE_ALWAYS     // enable action and do not call action_handler_t::update() anymore
            AST_ENABLE_FOR_IDB    // enable action for the current idb. Call action_handler_t::update() when a database is opened/closed
            AST_ENABLE_FOR_WIDGET // enable action for the current widget. Call action_handler_t::update() when a form gets/loses focus
            AST_ENABLE            // enable action - call action_handler_t::update() when anything changes

            AST_DISABLE_ALWAYS    // disable action and do not call action_handler_t::action() anymore
            AST_DISABLE_FOR_IDB   // analog of ::AST_ENABLE_FOR_IDB
            AST_DISABLE_FOR_WIDGET// analog of ::AST_ENABLE_FOR_WIDGET
            AST_DISABLE           // analog of ::AST_ENABLE
    """
    NAME = None
    TEXT = "Default. Replace me!"
    HOTKEY = ""
    TOOLTIP = ""
    ICON = -1

    @classmethod
    def get_name(cls):
        """Return the name of the action.

        If a name has not been set (using the `Name` class variable), the
        function generates a name based on the class name and id.
        :return: action name
        :rtype: str
        """
        if cls.NAME is not None:
            return cls.NAME

        return "{}:{}".format(cls.__name__, id(cls))

    @classmethod
    def get_desc(cls):
        """Get a descriptor for this handler."""
        name = cls.get_name()
        text = cls.TEXT
        handler = cls()
        hotkey = cls.HOTKEY
        tooltip = cls.TOOLTIP
        icon = cls.ICON
        action_desc = idaapi.action_desc_t(
            name,
            text,
            handler,
            hotkey,
            tooltip,
            icon,
        )
        return action_desc

    @classmethod
    def register(cls):
        """Register the action.

        Each action MUST be registered before it can be used. To remove the action
        use the `unregister` method.
        """
        action_desc = cls.get_desc()

        return idaapi.register_action(action_desc)

    @classmethod
    def unregister(cls):
        """Unregister the action.

        After unregistering the class cannot be used.
        """
        idaapi.unregister_action(cls.get_name())

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

    def activate(self, ctx):
        try:
            self._activate(ctx)
            return 1
        except:
            trace = traceback.format_exc()
            idaapi.msg("Action {!r} failed to activate. Traceback:\n{}".format(self.get_name(), trace))
            return 0

    def update(self, ctx):
        """Update the action.

        Optionally override this function.
        See IDA-SDK for more information.
        """
        return idaapi.AST_ENABLE_ALWAYS

    def _activate(self, ctx):
        """Activate the action.

        This function contains the action code itself. You MUST implement
        it in your class for the action to work.

        Args:
            ctx: The action context passed from IDA.
        """
        raise NotImplementedError()


class Argument:

    def __init__(self):
        self.name = ""
        self.enum = None
        self._logger = logging.getLogger(
            __name__ + '.' + self.__class__.__name__)


class Function:

    def __init__(self):
        self.name = ""
        self.arguments = []
        self._logger = logging.getLogger(
            __name__ + '.' + self.__class__.__name__)

    def __str__(self):
        return ("%s -- %s" % (self.name, self.arguments))

    def __repr__(self):
        return self.__str__()


def all_digits(val: str):
    return all(x in string.digits for x in val)


class FunctionMap:
    def __init__(self, data_dir: str):
        self.data_dir = data_dir
        self.enums = json.loads(open(f"{self.data_dir}/enums.json").read())

    @functools.lru_cache()
    def __contains__(self, funcname: str):
        return os.path.exists(f"{self.data_dir}/functions/{funcname}.json")

    @functools.lru_cache()
    def __getitem__(self, funcname: str):
        if funcname not in self:
            raise KeyError(f"{funcname} not found!")
        func = Function()
        with open(f"{self.data_dir}/functions/{funcname}.json", "r") as file:
            data = json.loads(file.read())
            func.name = data["name"]
            args = []
            for k, v in data["enums"].items():
                arg = Argument()
                arg.name = k
                arg.enum = v
                args.append(arg)

            func.arguments = args
            return func

    def expand_enum(self, enum: Dict[str, int], enum_id: str) -> Dict[str, int]:
        items = list(enum.items())
        if not all_digits(enum_id):
            if re.search(r"_[0-9]+$", enum_id):
                enum_id = re.sub(r"_[0-9]+$", "", enum_id)
            for k, v in items:
                del enum[k]
                if k == "0":
                    enum["NULL"] = v
                else:
                    enum[f"{enum_id}_{k}"] = v
            return enum

        for k, v in items:
            if k == "0":
                del enum[k]
                enum["NULL"] = v
        return enum

    def get_enum(self, name: str):
        enum = self.enums[name]
        return self.expand_enum(enum, name)


def make_import_names_callback(library_calls, library_addr):
    """ Return a callback function used by idaapi.enum_import_names(). """

    def callback(ea, name, ordinal):
        """ Callback function to retrieve code references to library calls. """
        if "@" in name:
            name = name.split("@")[0]
            ea = next(idautils.CodeRefsTo(ea, 0), None)
            if ea is None:
                return True
            ea = next(ida_funcs.get_func(ea).addresses())

        wrapper = idc.get_name_ea_simple("." + name)    
        if wrapper != idc.BADADDR:
            ea = wrapper
        else:
            wrapper = idc.get_name_ea_simple(name)
            if wrapper != idc.BADADDR:
                ea = wrapper
        library_calls[name] = []
        library_addr[name] = ea
        for ref in idautils.CodeRefsTo(ea, 0):
            library_calls[name].append(ref)
        return True  # True -> Continue enumeration

    return callback


def get_imports(library_calls, library_addr):
    """ Populate dictionaries with import information. """
    import_names_callback = make_import_names_callback(library_calls,
                                                       library_addr)
    for i in range(0, idaapi.get_import_module_qty()):
        idaapi.enum_import_names(i, import_names_callback)


def get_funcinfo(funcptr_addr):
    tif = ida_typeinf.tinfo_t()
    funcdata = ida_typeinf.func_type_data_t()

    if not ida_nalt.get_tinfo(tif, funcptr_addr):
        return None, None
    if not tif.is_funcptr():
        if tif.is_func():
            tif.get_func_details(funcdata)
            return False, funcdata
        return None, None
    if not tif.get_pointed_object().get_func_details(funcdata):
        return None, None
    return True, funcdata


@functools.lru_cache()
def get_or_add_enum(funcmap: FunctionMap, enum_id: str):
    enum_name = f"ENUM_{enum_id}"
    ida_enum_id = idc.get_enum(enum_name)
    if ida_enum_id == idaapi.BADADDR:
        ida_enum_id = idc.add_enum(-1, enum_name, idaapi.hex_flag())
        enum = funcmap.get_enum(enum_id)
        ida_typeinf.begin_type_updating(ida_typeinf.UTP_ENUM)
        for k, v in enum.items():
            res = idc.add_enum_member(ida_enum_id, k, v, -1)
            append = 1
            while res != 0 and append < 10:
                res = idc.add_enum_member(ida_enum_id, f"{k}_{append}", v, -1)
                append += 1
        ida_typeinf.end_type_updating(ida_typeinf.UTP_ENUM)
        return enum_name
    return enum_name

class Hooks(idaapi.UI_Hooks):
    def finish_populating_widget_popup(self, form, popup):
        type = idaapi.get_widget_type(form)
        if type == idaapi.BWN_DISASM or type == idaapi.BWN_PSEUDOCODE:
            idaapi.attach_action_to_popup(form, popup, AutoEnum.get_name(), '')

class AutoEnum(ActionHandler):
    TEXT = "Auto Enum"

    def _activate(self, ctx):
        main()

class Hooks2(idaapi.UI_Hooks):
    def finish_populating_widget_popup(self, form, popup):
        type = idaapi.get_widget_type(form)
        if type == idaapi.BWN_DISASM or type == idaapi.BWN_PSEUDOCODE:
            idaapi.attach_action_to_popup(form, popup, AutoEnumPerCall.get_name(), '')

class AutoEnumPerCall(ActionHandler):
    TEXT = "Auto Enum (per-call annotation)"

    def _activate(self, ctx):
        handle_call_specific_enums()


class AutoEnumPlugin(idaapi.plugin_t):
    flags = 0
    comment = 'Automatically detect standard enums'
    help = 'Automatically detect standard enums'
    wanted_name = 'Auto Enum'

    def init(self):
        print("[AutoEnum] Plugin loaded!")
        AutoEnum.register()
        AutoEnumPerCall.register()
        self.hooks = Hooks()
        self.hooks.hook()
        self.hooks2 = Hooks2()
        self.hooks2.hook()

        return idaapi.PLUGIN_KEEP
    
    def term(self):
        self.hooks.unhook()
        self.hooks2.unhook()
        AutoEnum.unregister()
        AutoEnumPerCall.unregister()
    
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return AutoEnumPlugin()


def main():
    handle = ida_hexrays.open_pseudocode(idc.here(), 0)
    library_calls = {}
    library_addr = {}
    get_imports(library_calls, library_addr)
    binary_type = "windows"
    if "ELF" in idaapi.get_file_type_name():
        binary_type = "linux"
    thisdir = os.path.dirname(__file__)
    func_map = FunctionMap(os.path.join(thisdir, "data", binary_type))
    functions = list(library_addr.items())
    BOOL = ida_typeinf.tinfo_t()
    BOOL.get_named_type(idaapi.get_idati(), "MACRO_BOOL")
    for name, addr in functions:
        is_ptr, funcdata = get_funcinfo(addr)
        if name[:-1] in func_map:
            library_addr[name[:-1]] = library_addr[name]
            name = name[:-1]
        in_map = name in func_map
        changed = False
        if not funcdata:
            continue
        for arg in funcdata:
            if arg.type.is_ptr():
                continue
            type_name = arg.type.get_type_name()
            if type_name is not None and type_name.lower() in ["bool"]:
                arg.type = BOOL
                changed = True
            elif in_map and arg.type.is_integral() and not arg.type.is_enum():
                func = func_map[name]
                matching_arg = next((a for a in func.arguments if a.name == arg.name), None)
                if matching_arg and matching_arg.enum is not None:
                    enum_name = get_or_add_enum(func_map, matching_arg.enum)
                    enum_type = ida_typeinf.tinfo_t()
                    enum_type.get_named_type(idaapi.get_idati(), enum_name)
                    arg.type = enum_type
                    changed = True
        if changed:
            print(f"Setting enums for {name}")
            ti = idaapi.tinfo_t()
            ti.create_func(funcdata)
            if is_ptr:
                tip = idaapi.tinfo_t()
                tip.create_ptr(ti)
                ida_typeinf.apply_tinfo(addr, tip, idaapi.TINFO_DEFINITE)
            else:
                ida_typeinf.apply_tinfo(addr, ti, idaapi.TINFO_DEFINITE)
    handle.refresh_view(True)

def find_parent_expr(cfunc, ea, expr_types) -> ida_hexrays.cexpr_t:
    citem: ida_hexrays.citem_t = cfunc.body.find_closest_addr(ea)
    cexpr: ida_hexrays.cexpr_t = citem.cexpr
    depth = 0
    while cexpr.op not in expr_types and depth < 5:
        citem = cfunc.body.find_parent_of(citem)
        if citem is None:
            return None
        cexpr = citem.cexpr
        depth += 1
    
    if depth == 5:
        return None
    
    return cexpr

def get_call(cfunc, ea) -> ida_hexrays.cexpr_t:
    return find_parent_expr(cfunc, ea, [ida_hexrays.cot_call])

class type_modifier_t(ida_hexrays.user_lvar_modifier_t):
    def __init__(self, mapping):
        ida_hexrays.user_lvar_modifier_t.__init__(self)
        self.mapping = mapping

    def modify_lvars(self, lvars):
        for lvar in lvars.lvvec:
            if lvar.name in self.mapping:
                lvar.type = self.mapping[lvar.name]
        return True

def set_type(cfunc: ida_hexrays.cfunc_t, cexpr: ida_hexrays.cexpr_t, new_type: str):
    tinfo = ida_typeinf.tinfo_t()
    result = ida_typeinf.parse_decl(tinfo, None, new_type, ida_typeinf.PT_TYP|ida_typeinf.PT_SIL)
    dereferenced = False
    def dereference():
        nonlocal dereferenced
        if dereferenced:
            return tinfo
        if not tinfo.is_ptr():
            raise TypeError(f"Attempt to set non pointer type '{new_type}' on reference at 0x{cexpr.ea:x}")
        dereferenced = True
        return tinfo.get_pointed_object()
    
    if result is None:
        raise TypeError(f"Failed to parse type '{new_type}'")
    if cexpr.opname == "cast":
        cexpr = cexpr.x
    if cexpr.opname == "ref":
        tinfo = dereference()
        cexpr = cexpr.x
    if cexpr.opname == "obj":
        tinfo = dereference()
        ida_typeinf.apply_tinfo(cexpr.obj_ea, tinfo, ida_typeinf.TINFO_DEFINITE)
        return
    elif cexpr.opname == "var":
        func_ea = cfunc.entry_ea
        var_name = cexpr.dstr()
        ida_hexrays.rename_lvar(func_ea, var_name, var_name + "_rt")
        ida_hexrays.modify_user_lvars(func_ea, type_modifier_t({
            (var_name + "_rt"): tinfo
        }))
        ida_hexrays.rename_lvar(func_ea, var_name + "_rt", var_name)
        return
    raise TypeError(f"Don't know how to set type for a '{cexpr.opname}' at 0x{cexpr.ea:x}")

def handle_call_specific_enums():
    # Only updates for single function (maybe)
    handle = ida_hexrays.open_pseudocode(idc.here(), 0)
    cfunc = ida_hexrays.decompile(idc.here())
    if cfunc is None:
        return
    library_calls = {}
    library_addr = {}
    get_imports(library_calls, library_addr)
    if "ELF" in idaapi.get_file_type_name():
        binary_type = "linux"
    else:
        # Windows not supported at the moment
        return
    thisdir = os.path.dirname(__file__)
    path = os.path.join(thisdir, "data", binary_type)
    func_map = FunctionMap(path)
    special = json.load(open(os.path.join(path, "special.json")))
    for func_name, func_data in special.items():
        if func_name not in library_calls:
            continue
        for call_ea in library_calls[func_name]:
            call = get_call(cfunc, call_ea)
            if call is None:
                continue
            args = call.a
            arg_idx = func_data["primary_idx"]
            target_arg_idx = func_data["secondary_idx"]
            if arg_idx >= len(args) or target_arg_idx >= len(args):
                continue
            arg_val = args[arg_idx]
            if arg_val.opname != 'num':
                if arg_val.opname == 'cast' and arg_val.x.opname == 'num':
                    arg_val = arg_val.x
                else:
                    continue
            arg_val = arg_val.numval()
            if "options" in func_data:
                arg_data = func_data["options"]
                if str(arg_val) not in arg_data:
                    continue
                enum_id = arg_data[str(arg_val)]
                enum_name = get_or_add_enum(func_map, enum_id)
                enum_id = idc.get_enum(enum_name)
                try:
                    idc.op_enum(args[target_arg_idx].ea, -1, enum_id)
                except:
                    # IDA 8
                    idc.op_enum(args[target_arg_idx].ea, -1, enum_id, 0)
            if "types" in func_data:
                arg_data = func_data["types"]
                if str(arg_val) not in arg_data:
                    continue
                try:
                    set_type(cfunc, args[target_arg_idx], arg_data[str(arg_val)])
                except Exception as e:
                    print("Failed to set type", e)

    handle.refresh_view(True)


repository: xerub/idastuff
file: arm64/Simp7.py
description: IDA Pro/Hexrays plugins
url: https://github.com/xerub/idastuff/blob/62b720c1b5f60114d89a8a214177716ce3266b07/arm64/Simp7.py
wanted_name: simpa64
comment: Simplifier
created_at: 2017-04-05 22:24:02+00:00
pushed_at: 2018-10-02 00:43:46+00:00
content:
#  AArch64 mov simplifier IDA7 plugin
#
#  Copyright (c) 2015, 2017 xerub
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# based on Rolf Rolles x86 deobfuscator http://www.msreverseengineering.com

import idaapi
import idc

ARM64_MOVE_I = idaapi.ARM_mov

def dump_cmd(insn):
	print "cs = %lx" % insn.cs
	print "ip = %lx" % insn.ip
	print "ea = %lx" % insn.ea
	print "itype = %lx" % insn.itype
	print "size = %lx" % insn.size
	print "auxpref = %lx" % insn.auxpref
	print "segpref = %x" % ord(insn.segpref)
	print "insnpref = %x" % ord(insn.insnpref)
	print "flags = %lx" % insn.flags

def dump_op(op):
	print "n = %lx" % op.n
	print "type = %lx" % op.type
	print "offb = %lx" % op.offb
	print "offo = %lx" % op.offo
	print "flags = %lx" % op.flags
	print "dtyp = %lx" % op.dtype
	print "reg = %lx" % op.reg
	print "phrase = %lx" % op.phrase
	print "value = %lx" % op.value
	print "addr = %lx" % op.addr
	print "specval = %lx" % op.specval
	print "specflag1 = %lx" % op.specflag1
	print "specflag2 = %lx" % op.specflag2
	print "specflag3 = %lx" % op.specflag3
	print "specflag4 = %lx" % op.specflag4

def HighestSetBit(N, imm):
	i = N - 1
	while i >= 0:
		if imm & (1 << i):
			return i
		i -= 1
	return -1

def ZeroExtendOnes(M, N):				# zero extend M ones to N width
	return (1 << M) - 1

def RORZeroExtendOnes(M, N, R):
	val = ZeroExtendOnes(M, N)
	return ((val >> R) & ((1 << (N - R)) - 1)) | ((val & ((1 << R) - 1)) << (N - R))

def Replicate(val, bits):
	ret = val
	shift = bits
	while shift < 64:				# XXX actually, it is either 32 or 64
		ret |= (val << shift)
		shift += bits
	return ret

def DecodeBitMasks(immN, imms, immr, immediate):
	len = HighestSetBit(7, (immN << 6) | (~imms & 0x3F))
	if len < 1:
		return None
	levels = ZeroExtendOnes(len, 6)
	if immediate and (imms & levels) == levels:
		return None
	S = imms & levels
	R = immr & levels
	esize = 1 << len
	return Replicate(RORZeroExtendOnes(S + 1, esize, R), esize)

def DecodeMov(opcode, total, first):
	# opc
	o = (opcode >> 29) & 3
	# constant
	k = (opcode >> 23) & 0x3F

	if k == 0x24 and o == 1:			# MOV (bitmask imm) <=> ORR (immediate)
		# sf
		s = (opcode >> 31) & 1
		# N
		N = (opcode >> 22) & 1
		if s == 0 and N != 0:
			return None
		# rn
		rn = (opcode >> 5) & 0x1F
		if rn == 31:
			imms = (opcode >> 10) & 0x3F
			immr = (opcode >> 16) & 0x3F
			return DecodeBitMasks(N, imms, immr, True)
	elif k == 0x25:					# MOVN/MOVZ/MOVK
		# sf
		s = (opcode >> 31) & 1
		# hw
		h = (opcode >> 21) & 3
		# imm16
		i = (opcode >> 5) & 0xFFFF
		if s == 0 and h > 1:
			return None
		h *= 16
		i <<= h
		if o == 0:				# MOVN
			return ~i
		elif o == 2:				# MOVZ
			return i
		elif o == 3 and not first:		# MOVK
			return (total & ~(0xFFFF << h)) | i
	elif (k | 1) == 0x23 and not first:		# ADD (immediate)
		# shift
		h = (opcode >> 22) & 3
		if h > 1:
			return None
		# rn
		rd = opcode & 0x1F
		rn = (opcode >> 5) & 0x1F
		if rd != rn:
			return None
		# imm12
		i = (opcode >> 10) & 0xFFF
		h *= 12
		i <<= h
		if o & 2:				# SUB
			return total - i
		else:					# ADD
			return total + i

	return None

def check_mov_sequence(ea):
	oldea = ea
	reg = -1
	total = 0
	is64 = False
	while idaapi.getseg(ea).use64():
		d = idaapi.get_dword(ea)
		# reg
		r = d & 0x1F
		if reg >= 0 and reg != r:
			break
		newval = DecodeMov(d, total, reg < 0)
		if newval is None:
			break
		if reg >= 0 and idaapi.get_first_fcref_to(ea) != idaapi.BADADDR:
			break
		if (d >> 31) & 1:
			is64 = True
		total = newval
		reg = r
		ea += 4
	return ea - oldea, reg, is64, total

def is_my_mov(insn):
	if insn.itype == ARM64_MOVE_I and insn.flags == idaapi.INSN_MACRO and insn.size > 4:
		return True
	return False

class simpA64Hook(idaapi.IDP_Hooks):
	def __init__(self):
		idaapi.IDP_Hooks.__init__(self)
		self.n = idaapi.netnode("$ A64 Simplifier",0,1)

	def ev_ana_insn(self, insn):
		len, reg, is64, imm = check_mov_sequence(insn.ea)
		if len > 4:
			#print "0x%x: MOV/MOVK %c%d, #0x%x" % (insn.ea, 'X' if is64 else 'W', reg, imm)
			#dump_cmd(insn)
			#dump_op(insn.Op1)
			#dump_op(insn.Op2)
			insn.itype = ARM64_MOVE_I
			insn.segpref = 14 # ARM Condition = ALways
			insn.Op1.type = idaapi.o_reg
			insn.Op1.dtype = idaapi.dt_qword if is64 else idaapi.dt_dword
			insn.Op1.reg = reg + 129 # Use Wn/Xn registers instead of Rn
			insn.Op2.type = idaapi.o_imm
			insn.Op2.dtype = idaapi.dt_qword if is64 else idaapi.dt_dword
			insn.Op2.value = imm
			insn.flags = idaapi.INSN_MACRO
			insn.size = len
			return True
		return False

	def ev_out_mnem(self, ctx): # totally optional
		if is_my_mov(ctx.insn):
			ctx.out_custom_mnem("MOVE", idaapi.get_inf_structure().indent)
			return 1
		return 0

class simpa64_t(idaapi.plugin_t):
	flags = idaapi.PLUGIN_PROC
	comment = "Simplifier"
	wanted_hotkey = "Alt-Z"
	help = "Runs transparently"
	wanted_name = "simpa64"
	hook = None
	enabled = 1

	def init(self):
		self.hook = None
		if idaapi.ph_get_id() != idaapi.PLFM_ARM or idaapi.BADADDR <= 0xFFFFFFFF:
			return idaapi.PLUGIN_SKIP

		self.hook = simpA64Hook()
		flag = self.hook.n.altval(0)
		if flag:
			self.enabled = flag - 1
		print "%s is %sabled" % (self.wanted_name, "en" if self.enabled else "dis")
		if self.enabled:
			self.hook.hook()
		return idaapi.PLUGIN_KEEP

	def run(self, arg):
		print "%sabling %s" % ("dis" if self.enabled else "en", self.wanted_name)
		if self.enabled:
			self.hook.unhook()
		else:
			self.hook.hook()
		self.enabled = self.enabled ^ 1
		self.hook.n.altset(0, self.enabled + 1)
		idc.Refresh()

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

def PLUGIN_ENTRY():
	return simpa64_t()


repository: ToolDelta-Basic/ToolDelta
file: tooldelta/plugin_load/classic_plugin/loader.py
description: 为网易《我的世界：中国版》特制的基岩版租赁服组件加载器
created_at: 2023-04-30 04:39:19+00:00
pushed_at: 2025-05-12 06:20:38+00:00
url:
https://github.com/ToolDelta-Basic/ToolDelta/blob/21307f7fc66b186f28a84b53da61ea769aa23181/tooldelta/plugin_load/classic_plugin/loader.py

content:
import importlib
import os
import sys
import traceback
from typing import TYPE_CHECKING, TypeVar

from ... import utils
from ...utils import cfg, fmts
from ...constants import (
    TOOLDELTA_PLUGIN_DIR,
    TOOLDELTA_CLASSIC_PLUGIN,
)
from ...version import SystemVersionException
from ..basic import plugin_is_enabled
from ..exceptions import NotValidPluginError
from . import event_cbs
from .plugin_cls import Plugin

if TYPE_CHECKING:
    from ... import ToolDelta
    from ...plugin_load.plugins import PluginGroup

loaded_plugin_modules = []
__cached_frame: "ToolDelta | None" = None

PLUGIN_CLS = TypeVar("PLUGIN_CLS", bound=Plugin)


# TODO: 会存储已删除的插件模块, 可能导致内存泄漏
def plugin_entry(
    plugin_cls: type[PLUGIN_CLS],
    api_name: str | list[str] = [],
    api_version: cfg.VERSION | None = None,
) -> PLUGIN_CLS:
    """
    实例化 ToolDelta 类式插件的主类

    Args:
        plugin_cls (type[Plugin]): 插件主类
        api_name (str | list[str], optional): 如果将插件作为 API 插件, 该参数为 API 名, 可以有多个
        api_version (VERSION, optional): 插件的 API 版本

    Raises:
        NotValidPluginError: 插件主类必须继承 Plugin 类

    Returns:
        Plugin: 插件主类
    """
    global __cached_frame
    try:
        if not Plugin.__subclasscheck__(plugin_cls):
            raise NotValidPluginError(f"插件主类必须继承 Plugin 类 而不是 {plugin_cls}")
    except TypeError as exc:
        raise NotValidPluginError(
            f"插件主类必须继承 Plugin 类 而不是 {plugin_cls.__class__}"
        ) from exc
    if __cached_frame is None:
        help(plugin_cls)
        exit()
    plugin_ins = plugin_cls(__cached_frame)
    if api_name:
        if isinstance(api_name, str):
            plugin_ins._api_names = [api_name]
        else:
            plugin_ins._api_names = api_name
        if api_version:
            plugin_ins._api_ver = api_version
        else:
            plugin_ins._api_ver = plugin_cls.version
    return plugin_ins


def help(plugin: type[Plugin]) -> None:
    """
    查看插件帮助.
    常用于查看 get_plugin_api() 方法获取到的插件实例的帮助.
    """
    plugin_docs = "<plugins.help>: " + plugin.name + "开放的 API 接口说明:\n"
    for attr_name, attr in plugin.__dict__.items():
        if not attr_name.startswith("__") and attr.__doc__ is not None:
            plugin_docs += (
                "\n §a" + attr_name + ":§f\n    " + attr.__doc__.replace("\n", "\n    ")
            )
    fmts.clean_print(plugin_docs)


def read_plugins(plugin_grp: "PluginGroup") -> None:
    """
    读取插件

    Args:
        plugin_grp (PluginGroup): 插件组
    """
    PLUGIN_PATH = os.path.join(TOOLDELTA_PLUGIN_DIR, TOOLDELTA_CLASSIC_PLUGIN)
    if PLUGIN_PATH not in sys.path:
        sys.path.append(PLUGIN_PATH)
    event_cbs.broadcast_listener.clear()
    event_cbs.dict_packet_funcs.clear()
    event_cbs.bytes_packet_funcs.clear()
    for plugin_dir in os.listdir(PLUGIN_PATH):
        if not plugin_is_enabled(plugin_dir):
            continue
        if os.path.isdir(os.path.join(PLUGIN_PATH, plugin_dir)):
            sys.path.append(os.path.join(PLUGIN_PATH, plugin_dir))
            load_plugin(plugin_grp, plugin_dir)
            if os.path.isfile(
                data_path := os.path.join(
                    "插件文件", TOOLDELTA_CLASSIC_PLUGIN, plugin_dir, "datas.json"
                )
            ):
                plugin_data = utils.safe_json.safe_json_load(data_path)
                plugin_grp.loaded_plugin_ids.append(plugin_data["plugin-id"])


def load_plugin(plugin_group: "PluginGroup", plugin_dirname: str) -> None | Plugin:
    """加载插件

    Args:
        plugin_group (PluginGroup): 插件组类
        plugin_dirname (str): 插件目录名
        hot_load (bool, optional): 是否热加载

    Raises:
        ValueError: 插件组未初始化读取
        ValueError: 插件组未绑定框架
        ValueError: 插件主类需要作者名
        ValueError: 插件主类需要作者名
        NotValidPluginError: 插件 不合法
        SystemExit: 插件名字不合法
        SystemExit: 插件配置文件报错
        SystemExit: 插件读取数据失败

    Returns:
        Union[None, Plugin]: 插件实例
    """
    global __cached_frame
    if isinstance(plugin_group, type(None)):
        raise ValueError("插件组未初始化读取")
    if isinstance(plugin_group.linked_frame, type(None)):
        raise ValueError("插件组未绑定框架")
    __cached_frame = plugin_group.linked_frame
    try:
        if os.path.isfile(
            os.path.join(
                "插件文件", TOOLDELTA_CLASSIC_PLUGIN, plugin_dirname, "__init__.py"
            )
        ):
            plugin_module = importlib.import_module(plugin_dirname)
            if plugin_module in loaded_plugin_modules:
                importlib.reload(plugin_module)
                mode_str = "重载"
            else:
                loaded_plugin_modules.append(plugin_module)
                mode_str = "载入"
        else:
            fmts.print_war(f"{plugin_dirname} 文件夹 未发现插件文件，跳过加载")
            return None
        plugin: Plugin | None = plugin_module.__dict__.get("entry")
        if not isinstance(plugin, Plugin):
            raise NotValidPluginError(
                "没有在最外层代码使用 entry = plugin_entry(YourPlugin) 语句注册插件"
            )
        if plugin.name is None or plugin.name == "":
            raise ValueError(f"插件主类 {plugin.__class__.__name__} 需要插件名")
        if len(plugin.version) != 3:
            raise NotValidPluginError(
                f"插件主类 {plugin.__class__.__name__} 的 version 属性需要是长度为 3 的元组, 如 (0, 0, 1)"
            )
        if plugin._api_names:
            # 此插件应该作为 API 插件
            for api_name in plugin._api_names:
                plugin_group.plugins_api[api_name] = plugin
        version_str = ".".join(map(str, plugin.version))
        plugin._plugin_group = plugin_group
        fmts.print_suc(
            f"已{mode_str}插件 §f{plugin.name}§b@{version_str} §a作者: §r{plugin.author}"
        )
        plugin_group.normal_plugin_loaded_num += 1
        return plugin
    except NotValidPluginError as err:
        fmts.print_err(f"插件 {plugin_dirname} 不合法：{err.args[0]}")
        raise SystemExit from err
    except cfg.ConfigError as err:
        fmts.print_err(f"插件 {plugin_dirname} 配置文件报错：{err}")
        fmts.print_err(
            "你也可以直接删除配置文件，重新启动 ToolDelta 以自动生成配置文件"
        )
        raise SystemExit from err
    except utils.safe_json.DataReadError as err:
        fmts.print_err(f"插件 {plugin_dirname} 读取数据失败：{err}")
    except SystemVersionException as err:
        fmts.print_err(f"插件 {plugin_dirname} 需要更高版本的 ToolDelta 加载：{err}")
        raise SystemExit
    except Exception as err:
        fmts.print_err(f"加载插件 {plugin_dirname} 出现问题，报错如下：")
        fmts.print_err("§c" + traceback.format_exc())
        raise SystemExit from err
    return None


repository: loyaltypollution/ida2llvm
file: ida2llvm_plugin.py
wanted_name: IDA2LLVM
comment: Microcode Lifter to LLVM
created_at: 2023-04-04 16:07:21+00:00
pushed_at: 2024-03-27 01:58:20+00:00
url:
https://github.com/loyaltypollution/ida2llvm/blob/1341592591b7a7795c4fa0f1441bf2eff97c11a1/ida2llvm_plugin.py

content:
import ida_idaapi
import ida_kernwin
import idautils
import ida_name
import ida_funcs
import ida_segment

from PyQt5 import QtCore, QtWidgets, QtGui

def PLUGIN_ENTRY():
    return IDA2LLVMPlugin()

class IDA2LLVMPlugin(ida_idaapi.plugin_t):

    flags = ida_idaapi.PLUGIN_PROC | ida_idaapi.PLUGIN_HIDE
    comment = "Microcode Lifter to LLVM"
    help = ""
    wanted_name = "IDA2LLVM"
    wanted_hotkey = ""

    def init(self):
        
        action = {
            'id': 'ida2llvm:view_lifting',
            'name': 'Lifting Viewer',
            'hotkey': 'Ctrl-Alt-L',
            'comment': 'UI for which function to lift',
            'menu_location': 'Edit/IDA2LLVM/Viewer'
        }
        if not ida_kernwin.register_action(ida_kernwin.action_desc_t(
            action['id'],
            action['name'], # The name the user sees
            IDA2LLVMController(), # The function to call
            action['hotkey'], # A shortcut, if any (optional)
            action['comment'], # A comment, if any (optional)
            -1
        )):
            print("ida2llvm: failed to register action")

        if not ida_kernwin.attach_action_to_menu(
            action['menu_location'], # The menu location
            action['id'], # The unique function ID
            0):
            print("ida2llvm: failed to attach to menu")

        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        ida_kernwin.warning("%s cannot be run as a script in IDA." % self.wanted_name)

    def term(self):
        pass

class IDA2LLVMController(ida_kernwin.action_handler_t):
    """
    The control component of BinaryLift Explorer.
    """
    def __init__(self):
        from llvmlite import ir
        ida_kernwin.action_handler_t.__init__(self)
        self.current_address = None

        class AddressHook(ida_kernwin.UI_Hooks):
            def __init__(self, controller):
                ida_kernwin.UI_Hooks.__init__(self)
                self.controller = controller
            def database_inited(self, is_new_database, idc_script):
                self.controller.cache = dict()
                self.controller.namecache = dict()
                self.controller.config = dict()
                self.controller.m = ir.Module()
            def screen_ea_changed(self, ea, prev_ea):
                self.controller.screen_ea = ea
                self.controller.view.refresh()
        
        self._hook = AddressHook(self)
        self._hook.hook()
        self.view = IDA2LLVMView(self)

    def activate(self, ctx):
        self.view.Show()
        return 1

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

    def isScreenEaInvalid(self):
        return ida_funcs.get_func(self.screen_ea) is None

    def resolveName(self, current_address):
        func_name = ida_name.get_name(current_address)
        if func_name != self.namecache.get(current_address, None):
            print("NAME NOT SYNCED, PROBABLY CHANGED")
        self.namecache[current_address] = func_name
        return self.namecache[current_address]

    def declareCurrentFunction(self, isDeclare):
        current_name = self.resolveName(self.current_address)
        self.config[self.current_address] = bool(isDeclare)
        self.cache[self.current_address] = self.getLiftedText()
        self.view.refresh()

    def updateFunctionSelected(self, selectName):
        if selectName == "":
            return
        current_address, _ = selectName.split(":", maxsplit=1)
        self.current_address = int(current_address, 16)
        current_name = self.resolveName(self.current_address)
        ida_kernwin.jumpto(self.current_address)
        ida_kernwin.activate_widget(self.view._twidget, True)

    def insertAllFunctions(self):
        for f_ea in idautils.Functions():
            name = ida_funcs.get_func_name(f_ea)
            if (ida_funcs.get_func(f_ea).flags & ida_funcs.FUNC_LIB
                or ida_segment.segtype(f_ea) & ida_segment.SEG_XTRN
                or name.startswith("_")):
                continue
            self.insertFunctionAtEa(f_ea)

    def insertFunctionAtScreenEa(self):
        if self.isScreenEaInvalid():
            return
        self.current_address = ida_funcs.get_func(self.screen_ea).start_ea
        self.insertFunctionAtEa(self.current_address)
        self.view.refresh()

    def insertFunctionAtEa(self, ea):
        temp_ea = self.current_address
        self.current_address = ea
        current_name = self.resolveName(self.current_address)

        if self.current_address not in self.config:
            self.config[self.current_address] = False
            
        self.cache[self.current_address] = self.getLiftedText()
        self.current_address = temp_ea

    def removeFromModule(self, func_name):
        from contextlib import suppress
        from llvmlite import ir
        with suppress(KeyError):
            old_func = self.m.globals[func_name]
            _m = ir.Module()
            for name, gv in self.m.globals.items():
                if name != func_name:
                    gv.parent = _m
                    _m.add_global(gv)
            self.m = _m

    def getLiftedText(self):
        import ida2llvm

        func_name = self.resolveName(self.current_address)
        isDeclare = self.config[self.current_address]
        self.removeFromModule(func_name)
        llvm_f = ida2llvm.function.lift_function(self.m, func_name, isDeclare)

        for f in self.m.functions:
            f_name = f.name
            f_ea = ida_name.get_name_ea(ida_idaapi.BADADDR, f_name)
            self.namecache[f_ea] = f_name
            self.config[f_ea] = f.is_declaration
            self.cache[f_ea] = str(f)

            name = f"{hex(f_ea)}: {f_name}"
            if not self.view.function_list.findItems(name, QtCore.Qt.MatchExactly):
                self.view.function_list.addItem(name)

        return str(llvm_f)

    def save_to_file(self):
       filename, _ = QtWidgets.QFileDialog.getSaveFileName(None, 'Save Lifted LLVM IR', '', 'LLVM IR (*.ll)')
       if filename:
           with open(filename, 'w') as f:
               f.write(str(self.m))

class IDA2LLVMView(ida_kernwin.PluginForm):
    """
    The view component of BinaryLift Explorer.
    """
    def __init__(self, controller):
        ida_kernwin.PluginForm.__init__(self)
        self.controller = controller
        self.created = False

    def Show(self):
        return ida_kernwin.PluginForm.Show(
            self, "IDA2LLVM Viewer",
            options=(ida_kernwin.PluginForm.WOPN_PERSIST |
                     ida_kernwin.PluginForm.WCLS_SAVE |
                     ida_kernwin.PluginForm.WOPN_MENU |
                     ida_kernwin.PluginForm.WOPN_RESTORE |
                     ida_kernwin.PluginForm.WOPN_TAB))

    def refresh(self):
        if not self.created:
            return
        self.lifting_settings.setDisabled(self.function_list.currentRow() == -1)
        self.curr_ea_button.setDisabled(self.controller.isScreenEaInvalid())
        if not self.controller.isScreenEaInvalid():
            self.curr_ea_button.setText(f"{'Redefine' if ida_funcs.get_func(self.controller.screen_ea).start_ea in self.controller.config else 'Add'} function at current address ({hex(self.controller.screen_ea)})")
        if self.controller.current_address:
            self.isDeclare.setChecked(self.controller.config[self.controller.current_address])
            self.code_view.setText(self.controller.cache[self.controller.current_address])

    def create_code_view(self):
        self.code_view = QtWidgets.QTextEdit(self.widget)

    def create_function_settings(self):
        self.isDeclare = QtWidgets.QCheckBox("Keep function as declare-only")
        self.isDeclare.setChecked(False)
        self.isDeclare.stateChanged.connect(self.controller.declareCurrentFunction)
        
        self.lifting_settings = QtWidgets.QGroupBox("Lift Settings")
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.isDeclare)
        self.lifting_settings.setLayout(layout)

    def create_function_list(self):
        controller = self.controller
        class FunctionListWidget(QtWidgets.QListWidget):
            def __init__(self, parent, *args, **kwargs):
                super().__init__(parent, *args, **kwargs)

                for address in controller.config:
                    current_name = controller.resolveName(address)
                    self.addItem(f"{hex(address)}: {current_name}")
            def keyPressEvent(self, event):
                if event.key() == QtCore.Qt.Key_Delete:
                    row = self.currentRow()
                    item = self.takeItem(row)
                    address, name = item.text().split(": ", maxsplit=1)
                    address = int(address, 16)
                    controller.removeFromModule(name)
                    del controller.cache[address]
                    del controller.namecache[address]
                    del controller.config[address]
                    del item
                else:
                    super().keyPressEvent(event)
        self.function_list = FunctionListWidget(self.widget)
        self.function_list.setSortingEnabled(True)
        self.function_list.currentTextChanged.connect(self.controller.updateFunctionSelected)

    def OnCreate(self, form):
        self._twidget = self.GetWidget()
        self.widget = self.FormToPyQtWidget(form)
        layout = QtWidgets.QGridLayout(self.widget)

        self.curr_ea_button         = QtWidgets.QPushButton("Add function at current address", self.widget)
        self.all_functions_button   = QtWidgets.QPushButton("Add all IDA-defined functions", self.widget)
        self.lift_button            = QtWidgets.QPushButton("Lift and save to file", self.widget)

        self.curr_ea_button.clicked.connect(self.controller.insertFunctionAtScreenEa)
        self.all_functions_button.clicked.connect(self.controller.insertAllFunctions)
        self.lift_button.clicked.connect(self.controller.save_to_file)

        self.create_code_view()
        self.create_function_settings()
        self.create_function_list()

        # arrange the widgets in a 'grid'         row  col  row span  col span
        layout.addWidget(self.code_view,            0,   0,        0,        1)
        layout.addWidget(self.function_list,        0,   1,        1,        1)
        layout.addWidget(self.lifting_settings,     1,   1,        1,        1)
        layout.addWidget(self.curr_ea_button,       2,   1,        1,        1)
        layout.addWidget(self.all_functions_button, 3,   1,        1,        1)
        layout.addWidget(self.lift_button,          4,   1,        1,        1)

        self.widget.setLayout(layout)
        self.created = True
        self.refresh()


repository: z1r00/dia2ol
file: diaphora/plugin/diaphora_plugin.py
description: online diff
wanted_name: Diaphora
comment: Diaphora by joxeankoret
created_at: 2024-12-11 02:37:26+00:00
pushed_at: 2024-12-31 02:47:04+00:00
url:
https://github.com/z1r00/dia2ol/blob/45e3fd5345e13ce389b51c2d6dcf21f30cb16920/diaphora/plugin/diaphora_plugin.py

content:
"""
Diaphora, a binary diffing tool
Copyright (c) 2015-2024, Joxean Koret

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

import os
import sys
import configparser

import idaapi
from idaapi import warning

#-------------------------------------------------------------------------------
def resolve_diaphora():
  config_dir = os.path.dirname(__file__)
  config_file = os.path.join(config_dir, "diaphora_plugin.cfg")
  if not os.path.exists(config_file):
    warning(f"The configuration file {config_file} does not exist.")
    return None
  
  config = configparser.ConfigParser()
  config.read(config_file)

  path = config["Diaphora"]["path"]
  sys.path.append(path)

  from diaphora_ida import main
  return main

#-------------------------------------------------------------------------------
class DiaphoraPlugin(idaapi.plugin_t):
  wanted_name = "Diaphora"
  version = "3.2.0"
  wanted_hotkey = ""
  comment = "Diaphora by joxeankoret"
  website = "https://github.com/joxeankoret/diaphora"
  help = "Export the current binary or diff against another binary"
  flags = 0

  def init(self):
    self.diaphora_main = None
    return idaapi.PLUGIN_KEEP

  def term(self):
    pass

  def run(self, arg):
    if self.diaphora_main is None:
      self.diaphora_main = resolve_diaphora()

    if self.diaphora_main is not None:
      self.diaphora_main()

    return True

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


repository: antkss/dots-hypr
file: .idapro/plugins/findfuncmain.py
description: helloworld
comment: Function Finder and Advanced copying of instruction bytes
created_at: 2024-03-07 13:41:25+00:00
pushed_at: 2025-04-19 17:51:03+00:00
url:
https://github.com/antkss/dots-hypr/blob/60a14e7b51f0436004848fdbcb7ac394c27a958b/.idapro/plugins/findfuncmain.py

content:
import sys

from PyQt5.QtWidgets import QApplication, QMessageBox, QVBoxLayout

from findfunc import findfunc_gui

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

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

__AUTHOR__ = 'feber'

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

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


# plugin stuff

def PLUGIN_ENTRY():
    return FindFunc()


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

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

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

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

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

        self.hooks.hook()

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

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

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


# plugin helper stuff

cursession = ""
lastsavedsession = ""


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


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

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

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


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

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

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


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

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


repository: wobbier/HEDZ
file: Ghidra/Extensions/IDAPro/Python/7xx/plugins/xml_importer.py
description: A decompile project of the old game HEDZ
wanted_name: XML Importer
comment: Import XML PROGRAM file
created_at: 2023-06-16 08:51:17+00:00
pushed_at: 2024-10-13 07:48:58+00:00
url:
https://github.com/wobbier/HEDZ/blob/ef3d177053730d30cb54df667d5fa28719b50201/Ghidra/Extensions/IDAPro/Python/7xx/plugins/xml_importer.py

content:
## ###
#  IP: GHIDRA
# 
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#  
#       http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
##
#---------------------------------------------------------------------
# xmlimp.py - IDA XML Importer plugin
#---------------------------------------------------------------------
"""
Plugin for IDA to import a XML PROGRAM file into an existing open database.
This file must be placed in the IDA plugins directory.
The file idaxml.py must be placed in the IDA python directory.
"""

from __future__ import print_function
import ida_idaapi
import ida_pro
import idaxml
import idc
import sys

if sys.version_info.major >= 3:
    from idaxml import _exc_info
    sys.exc_value = lambda: _exc_info()[1]
    sys.exc_type = lambda: _exc_info()[0]

class XmlImporterPlugin(ida_idaapi.plugin_t):
    """
    XML Importer plugin class
    """
    flags = 0
    comment = "Import XML PROGRAM file"
    help = "Import XML <PROGRAM> document to database"
    wanted_name = "XML Importer"
    wanted_hotkey = "Ctrl-Alt-l"

    def init(self):
        """
        init function for XML Importer plugin.
        
        Returns:
            Constant PLUGIN_OK if this IDA version supports the plugin,
            else returns PLUGIN_SKIP if this IDA is older than the supported
            baseline version.
        """
        if idaxml.is_ida_version_supported():
            return ida_idaapi.PLUGIN_OK 
        else:
            return ida_idaapi.PLUGIN_SKIP


    def run(self, arg):
        """
        run function for XML Importer plugin.
        
        Args:
            arg: Integer, a non-zero value enables auto-run feature for
                 IDA batch (no gui) processing mode. Default is 0.
        """
        st = idc.set_ida_state(idc.IDA_STATUS_WORK)
        xml = idaxml.XmlImporter(idaxml.PLUGIN, arg)
        try:
            try:
                xml.import_xml()
            except idaxml.Cancelled:
                msg = "XML Import cancelled!"
                print("\n" + msg)
                idc.warning(msg)
            except idaxml.MultipleAddressSpacesNotSupported:
                msg  = "XML Import cancelled!"
                msg += "\n\nXML Import does not currently support"
                msg += "\nimporting multiple address spaces."
                print("\n" + msg)
                idc.warning(msg)
            except:
                msg = "***** Exception occurred: XML Importer failed! *****"
                print("\n" + msg + "\n", sys.exc_type, sys.exc_value)
                idc.warning(msg)
        finally:
            xml.cleanup()
            idc.set_ida_state(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlImporterPlugin()


repository: icicle-emu/ghidra
file: GhidraBuild/IDAPro/Python/7xx/plugins/xml_importer.py
description: Icicle patches for Ghidra
wanted_name: XML Importer
comment: Import XML PROGRAM file
created_at: 2022-11-10 04:20:02+00:00
pushed_at: 2023-10-03 01:03:34+00:00
url:
https://github.com/icicle-emu/ghidra/blob/50230050fa58bd40d5a96cab9c167fc55bc92a76/GhidraBuild/IDAPro/Python/7xx/plugins/xml_importer.py

content:
## ###
#  IP: GHIDRA
# 
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#  
#       http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
##
#---------------------------------------------------------------------
# xmlimp.py - IDA XML Importer plugin
#---------------------------------------------------------------------
"""
Plugin for IDA to import a XML PROGRAM file into an existing open database.
This file must be placed in the IDA plugins directory.
The file idaxml.py must be placed in the IDA python directory.
"""

from __future__ import print_function
import ida_idaapi
import ida_pro
import idaxml
import idc
import sys

if sys.version_info.major >= 3:
    from idaxml import _exc_info
    sys.exc_value = lambda: _exc_info()[1]
    sys.exc_type = lambda: _exc_info()[0]

class XmlImporterPlugin(ida_idaapi.plugin_t):
    """
    XML Importer plugin class
    """
    flags = 0
    comment = "Import XML PROGRAM file"
    help = "Import XML <PROGRAM> document to database"
    wanted_name = "XML Importer"
    wanted_hotkey = "Ctrl-Alt-l"

    def init(self):
        """
        init function for XML Importer plugin.
        
        Returns:
            Constant PLUGIN_OK if this IDA version supports the plugin,
            else returns PLUGIN_SKIP if this IDA is older than the supported
            baseline version.
        """
        if idaxml.is_ida_version_supported():
            return ida_idaapi.PLUGIN_OK 
        else:
            return ida_idaapi.PLUGIN_SKIP


    def run(self, arg):
        """
        run function for XML Importer plugin.
        
        Args:
            arg: Integer, a non-zero value enables auto-run feature for
                 IDA batch (no gui) processing mode. Default is 0.
        """
        st = idc.set_ida_state(idc.IDA_STATUS_WORK)
        xml = idaxml.XmlImporter(idaxml.PLUGIN, arg)
        try:
            try:
                xml.import_xml()
            except idaxml.Cancelled:
                msg = "XML Import cancelled!"
                print("\n" + msg)
                idc.warning(msg)
            except idaxml.MultipleAddressSpacesNotSupported:
                msg  = "XML Import cancelled!"
                msg += "\n\nXML Import does not currently support"
                msg += "\nimporting multiple address spaces."
                print("\n" + msg)
                idc.warning(msg)
            except:
                msg = "***** Exception occurred: XML Importer failed! *****"
                print("\n" + msg + "\n", sys.exc_type, sys.exc_value)
                idc.warning(msg)
        finally:
            xml.cleanup()
            idc.set_ida_state(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlImporterPlugin()


repository: yashchaurasia667/Binary-Exploitation
file: Tools/ghidra_11.3.1_PUBLIC/Extensions/IDAPro/Python/7xx/plugins/xml_importer.py
wanted_name: XML Importer
comment: Import XML PROGRAM 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/7xx/plugins/xml_importer.py


repository: MassinissaDjellouli/CTFStuff
file: tools/ghidra_11.2.1_PUBLIC/Extensions/IDAPro/Python/7xx/plugins/xml_importer.py
wanted_name: XML Importer
comment: Import XML PROGRAM file
created_at: 2024-01-16 04:53:55+00:00
pushed_at: 2025-05-13 04:26:11+00:00
url:
https://github.com/MassinissaDjellouli/CTFStuff/blob/8be397cdd7b2b33bde74f33314fdcaf7244d36cb/tools/ghidra_11.2.1_PUBLIC/Extensions/IDAPro/Python/7xx/plugins/xml_importer.py


repository: shacojx/KeyPatch-IDA7dot0
file: keypatch.py
wanted_name: Keypatch Patcher
comment: Keypatch plugin for IDA Pro (using Keystone framework)
created_at: 2021-04-28 02:33:39+00:00
pushed_at: 2021-04-28 02:34:56+00:00
url:
https://github.com/shacojx/KeyPatch-IDA7dot0/blob/1fbb755f7efb93343e28e16f8ee5d1037a2269cc/keypatch.py

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

# Keypatch IDA Plugin, powered by Keystone Engine (http://www.keystone-engine.org).
# By Nguyen Anh Quynh & Thanh Nguyen, 2018.

# Keypatch is released under the GPL v2. See COPYING for more information.
# Find docs & latest version at http://keystone-engine.org/keypatch

# This IDA plugin includes 3 tools inside: Patcher, Fill Range & Search.
# Access to these tools via menu "Edit | Keypatch", or via right-click popup menu "Keypatch".

# Hotkey Ctrl-Alt-K opens either Patcher or "Fill Range" window, depending on context.
#  - If there is no code selection, hotkey opens Patcher dialog
#  - If a range of code is selected, hotkey opens "Fill Range" dialog

# To revert (undo) the last patching, choose menu "Edit | Keypatch | Undo last patching".
# To check for update version, choose menu "Edit | Keypatch | Check for update".

'''
    Do not use shortcut(Ctrk + Alt + K) for debugging, otherwise debugging features will not be available.
'''
#########################################################################################################
is_debug = False
if is_debug:
    ''' 
        Install pydevd:

        1. sudo pip install pydevd

        or

        2. Install pycharm-debug.egg, Ensure to use pycharm pro
        https://www.jetbrains.com/help/pycharm/remote-debugging-with-product.html

        # import site
        # site.addsitedir("/usr/local/lib/python2.7/site-packages")
    '''
    try:
        import pydevd

        pydevd.settrace(host='localhost',
                        port=51234,
                        stdoutToServer=True,
                        stderrToServer=True
                        )
    except Exception as e:
        print(e)
#########################################################################################################

import os
import re
import json
from keystone import *
import idc
import idaapi
from idc import GetOpType, GetOpnd, ItemEnd

# bleeding-edge version
# on a new release, this should be sync with VERSION_STABLE file
VERSION = "2.2"


MAX_INSTRUCTION_STRLEN = 256
MAX_ENCODING_LEN = 40
MAX_ADDRESS_LEN = 40
ENCODING_ERR_OUTPUT = "..."
KP_GITHUB_VERSION = "https://raw.githubusercontent.com/keystone-engine/keypatch/master/VERSION_STABLE"
KP_HOMEPAGE = "http://keystone-engine.org/keypatch"

X86_NOP = "\x90"

# Configuration file
KP_CFGFILE = os.path.join(idaapi.get_user_idadir(), "keypatch.cfg")

# save all the info on patching
patch_info = []


def to_hexstr(buf, sep=' '):
    return sep.join("{0:02x}".format(ord(c)) for c in buf).upper()


# return a normalized code, or None if input is invalid
def convert_hexstr(code):
    # normalize code
    code = code.lower()
    code = code.replace(' ', '')    # remove space
    code = code.replace('h', '')    # remove trailing 'h' in 90h
    code = code.replace('0x', '')   # remove 0x
    code = code.replace('\\x', '')  # remove \x
    code = code.replace(',', '')    # remove ,
    code = code.replace(';', '')    # remove ;
    code = code.replace('"', '')    # remove "
    code = code.replace("'", '')    # remove '
    code = code.replace("+", '')    # remove +

    # single-digit hexcode?
    if len(code) == 1 and ((code >= '0' and code <= '9') or (code >= 'a' and code <= 'f')):
        # stick 0 in front (so 'a' --> '0a')
        code = '0' + code

    # odd-length is invalid
    if len(code) % 2 != 0:
        return None

    try:
        hex_data = code.decode('hex')
        # we want a list of int
        return [ord(i) for i in hex_data]
    except:
        # invalid hex
        return None


# download a file from @url, then return (result, file-content)
# return (0, content) on success, or ({1|2}, None) on download failure
def url_download(url):
    from urllib2 import Request, urlopen, URLError, HTTPError

    # create the url and the request
    req = Request(url)

    # Open the url
    try:
        # download this URL
        f = urlopen(req)
        content = f.read()
        return (0, content)

    # handle errors
    except HTTPError, e:
        # print "HTTP Error:", e.code , url
        # fail to download this file
        return (1, None)
    except URLError, e:
        # print "URL Error:", e.reason , url
        # fail to download this file
        return (1, None)
    except Exception as e:
        # fail to save the downloaded file
        # print("Error:", e)
        return (2, None)


def get_name_value(_from, name):
    """
    Fixed: the return value truncated(32 bit) of get_name_value function that analyzed 64 bit binary file about ida64 for win.

    eg:
    type == idaapi.NT_BYTE
    (type, value) = idaapi.get_name_value(idc.BADADDR, "wcschr") # ida64 for win

    value = 0x14003d3f0L is correct  ida64 > 7.x for macOS
    value = 0x4003d3f0L is truncated ida64 >= 6.x for win, ida64 == 6.x for macOS

    :param _from: ea
    :param name: name string
    :return: tuple
    """
    (type, value) = idaapi.get_name_value(_from, name)
    if type == idaapi.NT_BYTE:  # type is byte name (regular name)
        value = idaapi.get_name_ea(_from, name)
    return (type, value)


## Main Keypatch class
class Keypatch_Asm:
    # supported architectures
    arch_lists = {
        "X86 16-bit": (KS_ARCH_X86, KS_MODE_16),                # X86 16-bit
        "X86 32-bit": (KS_ARCH_X86, KS_MODE_32),                # X86 32-bit
        "X86 64-bit": (KS_ARCH_X86, KS_MODE_64),                # X86 64-bit
        "ARM": (KS_ARCH_ARM, KS_MODE_ARM),                      # ARM
        "ARM Thumb": (KS_ARCH_ARM, KS_MODE_THUMB),              # ARM Thumb
        "ARM64 (ARMV8)": (KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN),# ARM64
        "Hexagon": (KS_ARCH_HEXAGON, KS_MODE_BIG_ENDIAN),       # Hexagon
        "Mips32": (KS_ARCH_MIPS, KS_MODE_MIPS32),               # Mips32
        "Mips64": (KS_ARCH_MIPS, KS_MODE_MIPS64),               # Mips64
        "PowerPC 32": (KS_ARCH_PPC, KS_MODE_PPC32),             # PPC32
        "PowerPC 64": (KS_ARCH_PPC, KS_MODE_PPC64),             # PPC64
        "Sparc 32": (KS_ARCH_SPARC, KS_MODE_SPARC32),           # Sparc32
        "Sparc 64": (KS_ARCH_SPARC, KS_MODE_SPARC64),           # Sparc64
        "SystemZ": (KS_ARCH_SYSTEMZ, KS_MODE_BIG_ENDIAN),       # SystemZ
    }

    endian_lists = {
        "Little Endian": KS_MODE_LITTLE_ENDIAN,                 # little endian
        "Big Endian": KS_MODE_BIG_ENDIAN,                       # big endian
    }

    syntax_lists = {
        "Intel": KS_OPT_SYNTAX_INTEL,
        "Nasm": KS_OPT_SYNTAX_NASM,
        "AT&T": KS_OPT_SYNTAX_ATT
    }

    def __init__(self, arch=None, mode=None):
        # update current arch and mode
        self.update_hardware_mode()

        # override arch & mode if provided
        if arch is not None:
            self.arch = arch
        if mode is not None:
            self.mode = mode

        # IDA uses Intel syntax by default
        self.syntax = KS_OPT_SYNTAX_INTEL

    # return Keystone arch & mode (with endianess included)
    @staticmethod
    def get_hardware_mode():
        (arch, mode) = (None, None)

        # heuristically detect hardware setup
        info = idaapi.get_inf_structure()
        
        try:
            cpuname = info.procname.lower()
        except:
            cpuname = info.procName.lower()

        try:
            # since IDA7 beta 3 (170724) renamed inf.mf -> is_be()/set_be()
            is_be = idaapi.cvar.inf.is_be()
        except:
            # older IDA versions
            is_be = idaapi.cvar.inf.mf
        # print("Keypatch BIG_ENDIAN = %s" %is_be)
        
        if cpuname == "metapc":
            arch = KS_ARCH_X86
            if info.is_64bit():
                mode = KS_MODE_64
            elif info.is_32bit():
                mode = KS_MODE_32
            else:
                mode = KS_MODE_16
        elif cpuname.startswith("arm"):
            # ARM or ARM64
            if info.is_64bit():
                arch = KS_ARCH_ARM64
                if is_be:
                    mode = KS_MODE_BIG_ENDIAN
                else:
                    mode = KS_MODE_LITTLE_ENDIAN
            else:
                arch = KS_ARCH_ARM
                # either big-endian or little-endian
                if is_be:
                    mode = KS_MODE_ARM | KS_MODE_BIG_ENDIAN
                else:
                    mode = KS_MODE_ARM | KS_MODE_LITTLE_ENDIAN
        elif cpuname.startswith("sparc"):
            arch = KS_ARCH_SPARC
            if info.is_64bit():
                mode = KS_MODE_SPARC64
            else:
                mode = KS_MODE_SPARC32
            if is_be:
                mode |= KS_MODE_BIG_ENDIAN
            else:
                mode |= KS_MODE_LITTLE_ENDIAN
        elif cpuname.startswith("ppc"):
            arch = KS_ARCH_PPC
            if info.is_64bit():
                mode = KS_MODE_PPC64
            else:
                mode = KS_MODE_PPC32
            if cpuname == "ppc":
                # do not support Little Endian mode for PPC
                mode += KS_MODE_BIG_ENDIAN
        elif cpuname.startswith("mips"):
            arch = KS_ARCH_MIPS
            if info.is_64bit():
                mode = KS_MODE_MIPS64
            else:
                mode = KS_MODE_MIPS32
            if is_be:
                mode |= KS_MODE_BIG_ENDIAN
            else:
                mode |= KS_MODE_LITTLE_ENDIAN
        elif cpuname.startswith("systemz") or cpuname.startswith("s390x"):
            arch = KS_ARCH_SYSTEMZ
            mode = KS_MODE_BIG_ENDIAN

        return (arch, mode)

    def update_hardware_mode(self):
        (self.arch, self.mode) = self.get_hardware_mode()

    # normalize assembly code
    # remove comment at the end of assembly code
    @staticmethod
    def asm_normalize(text):
        text = ' '.join(text.split())
        if text.rfind(';') != -1:
            return text[:text.rfind(';')].strip()

        return text.strip()

    @staticmethod
    # check if input address is valid
    # return
    #       -1  invalid address at target binary
    #        0  type mismatch of input address
    #        1  valid address at target binary
    def check_address(address):
        try:
            if idaapi.isEnabled(address):
                return 1
            else:
                return -1
        except:
            # invalid type
            return 0

    ### resolve IDA names from input asm code
    # todo: a better syntax parser for all archs
    def ida_resolve(self, assembly, address=idc.BADADDR):
        def _resolve(_op, ignore_kw=True):
            names = re.findall(r"[\$a-z0-9_:\.]+", _op, re.I)

            # try to resolve all names
            for name in names:
                # ignore known keywords
                if ignore_kw and name in ('byte', 'near', 'short', 'word', 'dword', 'ptr', 'offset'):
                    continue

                sym = name

                # split segment reg
                parts = name.partition(':')
                if parts[2] != '':
                    sym = parts[2]

                (type, value) = get_name_value(address, sym)

                # skip if name doesn't exist or segment / segment registers
                if type in (idaapi.NT_SEG, idaapi.NT_NONE):
                    continue

                _op = _op.replace(sym, '0x{0:X}'.format(value))

            return _op

        if self.check_address(address) == 0:
            print("Keypatch: WARNING: invalid input address {0}".format(address))
            return assembly

        # for now, we only support IDA name resolve for X86, ARM, ARM64, MIPS, PPC, SPARC
        if not (self.arch in (KS_ARCH_X86, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_PPC, KS_ARCH_SPARC)):
            return assembly

        _asm = assembly.partition(' ')
        mnem = _asm[0]
        opers = _asm[2].split(',')

        for idx, op in enumerate(opers):
            _op = list(op.partition('['))
            ignore_kw = True
            if _op[1] == '':
                _op[2] = _op[0]
                _op[0] = ''
            else:
                _op[0] = _resolve(_op[0], ignore_kw=True)
                ignore_kw = False

            _op[2] = _resolve(_op[2], ignore_kw=ignore_kw)

            opers[idx] = ''.join(_op)

        asm = "{0} {1}".format(mnem, ','.join(opers))
        return asm

    # return bytes of instruction or data
    # return None on failure
    def ida_get_item(self, address, hex_output=False):
        if self.check_address(address) != 1:
            # not a valid address
            return (None, 0)

        # return None if address is in the middle of instruction / data
        if address != idc.ItemHead(address):
            return (None, 0)

        len = idc.ItemSize(address)
        item = idaapi.get_many_bytes(address, len)

        if item is None:
            return (None, 0)

        if hex_output:
            item = to_hexstr(item)

        return (item, len)

    @staticmethod
    def get_op_dtype_name(op_idx):
        dtyp_lists = {
            idaapi.dt_byte: 'byte',     #  8 bit
            idaapi.dt_word: 'word',     #  16 bit
            idaapi.dt_dword: 'dword',   #  32 bit
            idaapi.dt_float: 'dword',   #  4 byte
            idaapi.dt_double: 'dword',  #  8 byte
            #idaapi.dt_tbyte = 5        #  variable size (ph.tbyte_size)
            #idaapi.dt_packreal = 6         #  packed real format for mc68040
            idaapi.dt_qword: 'qword',   #  64 bit
            idaapi.dt_byte16: 'xmmword',#  128 bit
            #idaapi.dt_code = 9         #  ptr to code (not used?)
            #idaapi.dt_void = 10        #  none
            #idaapi.dt_fword = 11       #  48 bit
            #idaapi.dt_bitfild = 12     #  bit field (mc680x0)
            #idaapi.dt_string = 13      #  pointer to asciiz string
            #idaapi.dt_unicode = 14     #  pointer to unicode string
            #idaapi.dt_3byte = 15       #  3-byte data
            #idaapi.dt_ldbl = 16        #  long double (which may be different from tbyte)
            idaapi.dt_byte32: 'ymmword',# 256 bit
        }

        dtype = idaapi.cmd.Operands[op_idx].dtyp
        dtyp_size = idaapi.get_dtyp_size(dtype)
        if dtype == idaapi.dt_tbyte:
            if dtyp_size == 10:
                return 'xword'

        dtyp_name = dtyp_lists.get(idaapi.cmd.Operands[op_idx].dtyp, None)

        return dtyp_name

    # return asm instructions from start to end
    def ida_get_disasm_range(self, start, end):
        codes = []
        while start < end:
            asm = self.asm_normalize(idc.GetDisasm(start))
            if asm == None:
                asm = ''
            codes.append(asm)
            start = start + idc.ItemSize(start)

        return codes

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

        def GetMnem(asm):
            sp = asm.find(' ')
            if sp == -1:
                return asm
            return asm[:sp]

        if self.check_address(address) != 1:
            # not a valid address
            return ''

        # return if address is in the middle of instruction / data
        if address != idc.ItemHead(address):
            return ''

        asm = self.asm_normalize(idc.GetDisasm(address))
        # for now, only support IDA syntax fixup for Intel CPU
        if not fixup or self.arch != KS_ARCH_X86:
            return asm

        # KS_ARCH_X86 mode
        # rebuild disasm code from IDA
        i = 0
        mnem = GetMnem(asm)
        if mnem == '' or mnem in ('rep', 'repne', 'repe'):
            return asm

        opers = []
        while GetOpType(address, i) > 0 and i < 6:
            t = GetOpType(address, i)
            o = GetOpnd(address, i)

            if t in (idc.o_mem, idc.o_displ):
                parts = list(o.partition(':'))
                if parts[2] == '':
                    parts[2] = parts[0]
                    parts[0] = ''

                if '[' not in parts[2]:
                    parts[2] = '[{0}]'.format(parts[2])

                o = ''.join(parts)

                if 'ptr ' not in o:
                    dtyp_name = self.get_op_dtype_name(i)
                    if dtyp_name != None:
                        o = "{0} ptr {1}".format(dtyp_name, o)

            opers.append(o)
            i += 1

        asm = mnem
        for o in opers:
            if o != '':
                asm = "{0} {1},".format(asm, o)

        asm = asm.strip(',')
        return asm

    # assemble code with Keystone
    # return (encoding, count), or (None, 0) on failure
    def assemble(self, assembly, address, arch=None, mode=None, syntax=None):

        # return assembly with arithmetic equation evaluated
        def eval_operand(assembly, start, stop, prefix=''):
            imm = assembly[start+1:stop]
            try:
                eval_imm = eval(imm)
                if eval_imm > 0x80000000:
                    eval_imm = 0xffffffff - eval_imm
                    eval_imm += 1
                    eval_imm = -eval_imm
                return assembly.replace(prefix + imm, prefix + hex(eval_imm))
            except:
                return assembly

        # IDA uses different syntax from Keystone
        # sometimes, we can convert code to be consumable by Keystone
        def fix_ida_syntax(assembly):

            # return True if this insn needs to be fixed
            def check_arm_arm64_insn(arch, mnem):
                if arch == KS_ARCH_ARM:
                    if mnem.startswith("ldr") or mnem.startswith("str"):
                        return True
                    return False
                elif arch == KS_ARCH_ARM64:
                    if mnem.startswith("ldr") or mnem.startswith("str"):
                        return True
                    return mnem in ("stp")
                return False

            # return True if this insn needs to be fixed
            def check_ppc_insn(mnem):
                return mnem in ("stw")

            # replace the right most string occurred
            def rreplace(s, old, new):
                li = s.rsplit(old, 1)
                return new.join(li)

            # convert some ARM pre-UAL assembly to UAL, so Keystone can handle it
            # example: streqb --> strbeq
            def fix_arm_ual(mnem, assembly):
                # TODO: this is not an exhaustive list yet
                if len(mnem) != 6:
                    return assembly

                if (mnem[-1] in ('s', 'b', 'h', 'd')):
                    #print(">> 222", mnem[3:5])
                    if mnem[3:5] in ("cc", "eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", "hi", "ls", "ge", "lt", "gt", "le", "al"):
                        return assembly.replace(mnem, mnem[:3] + mnem[-1] + mnem[3:5], 1)

                return assembly

            if self.arch != KS_ARCH_X86:
                assembly = assembly.lower()
            else:
                # Keystone does not support immediate 0bh, but only 0Bh
                assembly = assembly.upper()

            # however, 0X must be converted to 0x
            # Keystone should fix this limitation in the future
            assembly = assembly.replace("0X", " 0x")

            _asm = assembly.partition(' ')
            mnem = _asm[0]
            if mnem == '':
                return assembly

            # for PPC, Keystone does not accept registers with 'r' prefix,
            # but only the number behind. lets try to fix that here by
            # removing the prefix 'r'.
            if self.arch == KS_ARCH_PPC:
                for n in range(32):
                    r = " r%u," %n
                    if r in assembly:
                        assembly = assembly.replace(r, " %u," %n)
                for n in range(32):
                    r = "(r%u)" %n
                    if r in assembly:
                        assembly = assembly.replace(r, "(%u)" %n)
                for n in range(32):
                    r = ", r%u" %n
                    if assembly.endswith(r):
                        assembly = rreplace(assembly, r, ", %u" %n)

            if self.arch == KS_ARCH_X86:
                if mnem == "RETN":
                    # replace retn with ret
                    return assembly.replace('RETN', 'RET', 1)
                if 'OFFSET ' in assembly:
                    return assembly.replace('OFFSET ', ' ')
                if mnem in ('CALL', 'JMP') or mnem.startswith('LOOP'):
                    # remove 'NEAR PTR'
                    if ' NEAR PTR ' in assembly:
                        return assembly.replace(' NEAR PTR ', ' ')
                elif mnem[0] == 'J':
                    # JMP instruction
                    if ' SHORT ' in assembly:
                        # remove ' short '
                        return assembly.replace(' SHORT ', ' ')
            elif self.arch in (KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_PPC):
                # *** ARM
                # LDR     R1, [SP+rtld_fini],#4
                # STR     R2, [SP,#-4+rtld_fini]!
                # STR     R0, [SP,#fini]!
                # STR     R12, [SP,#4+var_8]!

                # *** ARM64
                # STP     X29, X30, [SP,#-0x10+var_150]!
                # STR     W0, [X29,#0x150+var_8]
                # LDR     X0, [X0,#(qword_4D6678 - 0x4D6660)]
                # TODO:
                # ADRP    X19, #interactive@PAGE

                # *** PPC
                # stw     r5, 0x120+var_108(r1)
                
                if self.arch == KS_ARCH_ARM and mode == KS_MODE_THUMB:
                    assembly = assembly.replace('movt.w', 'movt')

                if self.arch == KS_ARCH_ARM:
                    #print(">> before UAL fix: ", assembly)
                    assembly = fix_arm_ual(mnem, assembly)
                    #print(">> after UAL fix: ", assembly)

                if check_arm_arm64_insn(self.arch, mnem) or (("[" in assembly) and ("]" in assembly)):
                    bang = assembly.find('#')
                    bracket = assembly.find(']')
                    if bang != -1 and bracket != -1 and bang < bracket:
                        return eval_operand(assembly, bang, bracket, '#')
                    elif '+0x0]' in assembly:
                        return assembly.replace('+0x0]', ']')
                elif check_ppc_insn(mnem):
                    start = assembly.find(', ')
                    stop = assembly.find('(')
                    if start != -1 and stop != -1 and start < stop:
                        return eval_operand(assembly, start, stop)
            return assembly

        def is_thumb(address):
            return idc.GetReg(address, 'T') == 1

        if self.check_address(address) == 0:
            return (None, 0)

        # use default syntax, arch and mode if not provided
        if syntax is None:
            syntax = self.syntax
        if arch is None:
            arch = self.arch
        if mode is None:
            mode = self.mode

        if arch == KS_ARCH_ARM and is_thumb(address):
            mode = KS_MODE_THUMB

        try:
            ks = Ks(arch, mode)
            if arch == KS_ARCH_X86:
                ks.syntax = syntax
            encoding, count = ks.asm(fix_ida_syntax(assembly), address)
        except KsError as e:
            # keep the below code for debugging
            #print("Keypatch Error: {0}".format(e))
            #print("Original asm: {0}".format(assembly))
            #print("Fixed up asm: {0}".format(fix_ida_syntax(assembly)))
            encoding, count = None, 0

        return (encoding, count)


    # patch at address, return the number of written bytes & original data
    # this process can fail in some cases
    @staticmethod
    def patch_raw(address, patch_data, len):
        ea = address
        orig_data = ''

        while ea < (address + len):

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

            orig_byte = idc.Byte(ea)
            orig_data += chr(orig_byte)
            patch_byte = ord(patch_data[ea - address])

            if patch_byte != orig_byte:
                # patch one byte
                if idaapi.patch_byte(ea, patch_byte) != 1:
                    print("Keypatch: FAILED to patch byte at 0x{0:X} [0x{1:X}]".format(ea, patch_byte))
                    break
            ea += 1
        return (ea - address, orig_data)

    # patch at address, return the number of written bytes & original data
    # on patch failure, we revert to the original code, then return (None, None)
    def patch(self, address, patch_data, len):
        # save original function end to fix IDA re-analyze issue after patching
        orig_func_end = idc.GetFunctionAttr(address, idc.FUNCATTR_END)

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

        if len != patched_len:
            # patch failure
            if patched_len > 0:
                # revert the changes
                (rlen, _) = self.patch_raw(address, orig_data, patched_len)
                if rlen == patched_len:
                    print("Keypatch: successfully reverted changes of {0:d} byte(s) at 0x{1:X} [{2}]".format(
                                        patched_len, address, to_hexstr(orig_data)))
                else:
                    print("Keypatch: FAILED to revert changes of {0:d} byte(s) at 0x{1:X} [{2}]".format(
                                        patched_len, address, to_hexstr(orig_data)))

            return (None, None)

        # ask IDA to re-analyze the patched area
        if orig_func_end == idc.BADADDR:
            # only analyze patched bytes, otherwise it would take a lot of time to re-analyze the whole binary
            idaapi.analyze_area(address, address + patched_len + 1)
        else:
            idaapi.analyze_area(address, orig_func_end)

            # try to fix IDA function re-analyze issue after patching
            idaapi.func_setend(address, orig_func_end)

        return (patched_len, orig_data)

    # return number of bytes patched
    # return
    #    0  Invalid assembly
    #   -1  PatchByte failure
    #   -2  Can't read original data
    #   -3  Invalid address
    def patch_code(self, address, assembly, syntax, padding, save_origcode, orig_asm=None, patch_data=None, patch_comment=None, undo=False):
        global patch_info

        if self.check_address(address) != 1:
            # not a valid address
            return -3

        orig_comment = idc.Comment(address)
        if orig_comment is None:
            orig_comment = ''

        nop_comment = ""
        padding_len = 0
        if not undo:
            # we are patching via Patcher
            (orig_encoding, orig_len) = self.ida_get_item(address)
            if (orig_encoding, orig_len) == (None, 0):
                return -2

            (encoding, count) = self.assemble(assembly, address, syntax=syntax)
            if encoding is None:
                return 0

            patch_len = len(encoding)
            patch_data = ''.join(chr(c) for c in encoding)

            if patch_data == orig_encoding:
                #print("Keypatch: no need to patch, same encoding data [{0}] at 0x{1:X}".format(to_hexstr(orig_encoding), address))
                return orig_len

            # for now, only support NOP padding on Intel CPU
            if padding and self.arch == KS_ARCH_X86:
                if patch_len < orig_len:
                    padding_len = orig_len - patch_len
                    patch_len = orig_len
                    patch_data = patch_data.ljust(patch_len, X86_NOP)
                elif patch_len > orig_len:
                    patch_end = address + patch_len - 1
                    ins_end = ItemEnd(patch_end)
                    padding_len = ins_end - patch_end - 1

                    if padding_len > 0:
                        patch_len = ins_end - address
                        patch_data = patch_data.ljust(patch_len, X86_NOP)

                if padding_len > 0:
                    nop_comment = "\nKeypatch padded NOP to next boundary: {0} bytes".format(padding_len)

            orig_asm = self.ida_get_disasm_range(address, address + patch_len)
        else:
            # we are reverting the change via "Undo" menu
            patch_len = len(patch_data)

        (plen, p_orig_data) = self.patch(address, patch_data, patch_len)
        if plen is None:
            # failed to patch
            return -1

        if not undo: # we are patching
            new_patch_comment = None
            if save_origcode is True:
                # append original instruction to comments
                if orig_comment == '':
                    new_patch_comment = "Keypatch modified this from:\n  {0}{1}".format('\n  '.join(orig_asm), nop_comment)
                else:
                    new_patch_comment = "\nKeypatch modified this from:\n  {0}{1}".format('\n  '.join(orig_asm), nop_comment)

                new_comment = "{0}{1}".format(orig_comment, new_patch_comment)
                idc.MakeComm(address, new_comment)

            if padding_len == 0:
                print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen,
                                        address, to_hexstr(p_orig_data), to_hexstr(patch_data)))
            else:
                print("Keypatch: successfully patched {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}], with {4} byte(s) NOP padded".format(plen,
                                        address, to_hexstr(p_orig_data), to_hexstr(patch_data), padding_len))
            # save this patching for future "undo"
            patch_info.append((address, assembly, p_orig_data, new_patch_comment))
        else:   # we are reverting
            if patch_comment:
                # clean previous IDA comment by replacing it with ''
                new_comment = orig_comment.replace(patch_comment, '')
                idc.MakeComm(address, new_comment)

            print("Keypatch: successfully reverted {0:d} byte(s) at 0x{1:X} from [{2}] to [{3}]".format(plen,
                                        address, to_hexstr(p_orig_data), to_hexstr(patch_data)))

        return plen

    # fill a range of code [addr_begin, addr_end].
    # return the length of patched area
    # on failure, return 0 = wrong input, -1 = failed to patch
    def fill_code(self, addr_begin, addr_end, assembly, syntax, padding, save_origcode, orig_asm=None):
        # treat input as assembly code first
        (encoding, _) =  self.assemble(assembly, addr_begin, syntax=syntax)

        if encoding is None:
            # input might be a hexcode string. try to convert it to raw bytes
            encoding = convert_hexstr(assembly)

        if encoding is None:
            # invalid input: this is neither assembly nor hexcode string
            return 0

        # save original assembly code before overwritting them
        orig_asm = self.ida_get_disasm_range(addr_begin, addr_end)

        # save original comment at addr_begin
        # TODO: save comments in this range, but how to interleave them?
        orig_comment = idc.Comment(addr_begin)
        if orig_comment is None:
            orig_comment = ''

        patch_data = ""
        assembly_new = []
        size = addr_end - addr_begin
        # calculate filling data
        encode_chr = ''.join(chr(c) for c in encoding)
        while True:
            if len(patch_data) + len(encode_chr) <= size:
                patch_data = patch_data + encode_chr
                assembly_new += [assembly.strip()]
            else:
                break

        # for now, only support NOP padding on Intel CPU
        if padding and self.arch == KS_ARCH_X86:
            for i in range(size -len(patch_data)):
                assembly_new += ["nop"]
            patch_data = patch_data.ljust(size, X86_NOP)

        (plen, p_orig_data) = self.patch(addr_begin, patch_data, len(patch_data))
        if plen is None:
            # failed to patch
            return -1

        new_patch_comment = ''
        # append original instruction to comments
        if save_origcode is True:
            if orig_comment == '':
                new_patch_comment = "Keypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n  {3}".format(addr_begin, addr_end - 1, addr_end - addr_begin, '\n  '.join(orig_asm))
            else:
                new_patch_comment = "\nKeypatch filled range [0x{0:X}:0x{1:X}] ({2} bytes), replaced:\n  {3}".format(addr_begin, addr_end - 1, addr_end - addr_begin, '\n  '.join(orig_asm))

            new_comment = "{0}{1}".format(orig_comment, new_patch_comment)
            idc.MakeComm(addr_begin, new_comment)

        print("Keypatch: successfully filled range [0x{0:X}:0x{1:X}] ({2} bytes) with \"{3}\", replaced \"{4}\"".format(
                    addr_begin, addr_end - 1, addr_end - addr_begin, assembly, '; '.join(orig_asm)))

        # save this modification for future "undo"
        patch_info.append((addr_begin, '\n  '.join(assembly_new), p_orig_data, new_patch_comment))

        return plen


    ### Form helper functions
    @staticmethod
    def dict_to_ordered_list(dictionary):
        list = sorted(dictionary.items(), key=lambda t: t[0], reverse=False)
        keys = [i[0] for i in list]
        values = [i[1] for i in list]

        return (keys, values)

    def get_value_by_idx(self, dictionary, idx, default=None):
        (keys, values) = self.dict_to_ordered_list(dictionary)

        try:
            val = values[idx]
        except IndexError:
            val = default

        return val

    def find_idx_by_value(self, dictionary, value, default=None):
        (keys, values) = self.dict_to_ordered_list(dictionary)

        try:
            idx = values.index(value)
        except:
            idx = default

        return idx

    def get_arch_by_idx(self, idx):
        return self.get_value_by_idx(self.arch_lists, idx)

    def find_arch_idx(self, arch, mode):
        return self.find_idx_by_value(self.arch_lists, (arch, mode))

    def get_syntax_by_idx(self, idx):
        return self.get_value_by_idx(self.syntax_lists, idx, self.syntax)

    def find_syntax_idx(self, syntax):
        return self.find_idx_by_value(self.syntax_lists, syntax)
    ### /Form helper functions


# Common ancestor form to be derived by Patcher, FillRange & Search
class Keypatch_Form(idaapi.Form):
    # prepare for form initializing
    def setup(self, kp_asm, address, assembly=None):
        self.kp_asm = kp_asm
        self.address = address

        # update ordered list of arch and syntax
        self.syntax_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.syntax_lists)[0]
        self.arch_keys = self.kp_asm.dict_to_ordered_list(self.kp_asm.arch_lists)[0]

        # update current arch & mode
        self.kp_asm.update_hardware_mode()

        # find right value for c_arch & c_endian controls
        mode = self.kp_asm.mode
        self.endian_id = 0   # little endian
        if self.kp_asm.mode & KS_MODE_BIG_ENDIAN:
            self.endian_id = 1   # big endian
            mode = self.kp_asm.mode - KS_MODE_BIG_ENDIAN

        self.arch_id = self.kp_asm.find_arch_idx(self.kp_asm.arch, mode)

        self.syntax_id = 0  # to make non-X86 arch happy
        if self.kp_asm.arch == KS_ARCH_X86:
            self.syntax_id = self.kp_asm.find_syntax_idx(self.kp_asm.syntax)

        # get original instruction and bytes
        self.orig_asm = kp_asm.ida_get_disasm(address)
        (self.orig_encoding, self.orig_len) = kp_asm.ida_get_item(address, hex_output=True)
        if self.orig_encoding is None:
            self.orig_encoding = ''

        if assembly is None:
            self.asm = self.kp_asm.ida_get_disasm(self.address, fixup=True)
        else:
            self.asm = assembly

    # update Encoding control
    # return True on success, False on failure
    def _update_encoding(self, arch, mode):
        try:
            syntax = None
            if arch == KS_ARCH_X86:
                syntax_id = self.GetControlValue(self.c_syntax)
                syntax = self.kp_asm.get_syntax_by_idx(syntax_id)

            address = self.GetControlValue(self.c_addr)
            try:
                idaapi.isEnabled(address)
            except:
                # invalid address value
                address = 0

            assembly = self.GetControlValue(self.c_assembly)
            raw_assembly = self.kp_asm.ida_resolve(assembly, address)
            self.SetControlValue(self.c_raw_assembly, raw_assembly)

            (encoding, count) =  self.kp_asm.assemble(raw_assembly, address, arch=arch,
                                                    mode=mode, syntax=syntax)

            if encoding is None:
                self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT)
                self.SetControlValue(self.c_encoding_len, 0)
                return False
            else:
                text = ""
                for byte in encoding:
                    text += "%02X " % byte
                text.strip()
                if text == "":
                    # error?
                    self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT)
                    return False
                else:
                    self.SetControlValue(self.c_encoding, text.strip())
                    self.SetControlValue(self.c_encoding_len, len(encoding))
                    return True
        except Exception,e:
            print (str(e))
            import traceback
            traceback.print_exc()
            self.SetControlValue(self.c_encoding, ENCODING_ERR_OUTPUT)
            return False

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        return 1

    # update Patcher & Fillrange controls
    def update_patchform(self, fid):
        self.EnableField(self.c_endian, False)
        self.EnableField(self.c_addr, False)

        (arch, mode) = (self.kp_asm.arch, self.kp_asm.mode)
        # assembly is focused
        self.SetFocusedField(self.c_assembly)

        if arch == KS_ARCH_X86:
            # do not show Endian control
            self.ShowField(self.c_endian, False)
            # allow to choose Syntax
            self.ShowField(self.c_syntax, True)
            self.ShowField(self.c_opt_padding, True)
        else:   # do not show Syntax control for non-X86 mode
            self.ShowField(self.c_syntax, False)
            # for now, we do not support padding for non-X86 archs
            self.ShowField(self.c_opt_padding, False)
            #self.EnableField(self.c_opt_padding, False)

        # update other controls & Encoding with live assembling
        self.update_controls(arch, mode)

        return 1

    # update some controls - including Encoding control
    def update_controls(self, arch, mode):
        # Fixup & Encoding-len are read-only controls
        self.EnableField(self.c_raw_assembly, False)
        self.EnableField(self.c_encoding_len, False)

        # Encoding is enable to allow user to select & copy
        self.EnableField(self.c_encoding, True)

        if self.GetControlValue(self.c_endian) == 1:
            endian = KS_MODE_BIG_ENDIAN
        else:
            endian = KS_MODE_LITTLE_ENDIAN

        # update encoding with live assembling
        self._update_encoding(arch, mode | endian)

        return 1

    # get Patcher/FillRange options
    def get_opts(self, name=None):
        names = self.c_opt_chk.children_names
        val = self.c_opt_chk.value
        opts = {}
        for i in range(len(names)):
            opts[names[i]] = val & (2**i)

        if name != None:
            opts[name] = val

        return opts


# Fill Range form
class Keypatch_FillRange(Keypatch_Form):
    def __init__(self, kp_asm, addr_begin, addr_end, assembly=None, opts=None):
        self.setup(kp_asm, addr_begin, assembly)
        self.addr_end = addr_end

        # create FillRange form
        super(Keypatch_FillRange, self).__init__(
            r"""STARTITEM {id:c_assembly}
BUTTON YES* Patch
KEYPATCH:: Fill Range

            {FormChangeCb}
            <Endian     :{c_endian}>
            <~S~yntax     :{c_syntax}>
            <Start      :{c_addr}>
            <End        :{c_addr_end}>
            <Size       :{c_size}>
            <~A~ssembly   :{c_assembly}>
             <##-   Fixup :{c_raw_assembly}>
             <##-   Encode:{c_encoding}>
             <##-   Size  :{c_encoding_len}>
            <~N~OPs padding until next instruction boundary:{c_opt_padding}>
            <Save ~o~riginal instructions in IDA comment:{c_opt_comment}>{c_opt_chk}>
            """, {
            'c_endian': self.DropdownListControl(
                          items = self.kp_asm.endian_lists.keys(),
                          readonly = True,
                          selval = self.endian_id),
            'c_addr': self.NumericInput(value=addr_begin, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_addr_end': self.NumericInput(value=addr_end - 1, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_size': self.NumericInput(value=addr_end - addr_begin, swidth=8, tp=self.FT_DEC),
            'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN),
            'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN),
            'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC),
            'c_syntax': self.DropdownListControl(
                          items = self.syntax_keys,
                          readonly = True,
                          selval = self.syntax_id),
            'c_opt_chk':idaapi.Form.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']),
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        # make some controls read-only in FillRange mode
        self.EnableField(self.c_size, False)
        self.EnableField(self.c_addr_end, False)

        return self.update_patchform(fid)


# Patcher form
class Keypatch_Patcher(Keypatch_Form):
    def __init__(self, kp_asm, address, assembly=None, opts=None):
        self.setup(kp_asm, address, assembly)

        # create Patcher form
        super(Keypatch_Patcher, self).__init__(
            r"""STARTITEM {id:c_assembly}
BUTTON YES* Patch
KEYPATCH:: Patcher

            {FormChangeCb}
            <Endian     :{c_endian}>
            <~S~yntax     :{c_syntax}>
            <Address    :{c_addr}>
            <Original   :{c_orig_assembly}>
             <##-   Encode:{c_orig_encoding}>
             <##-   Size  :{c_orig_len}>
            <~A~ssembly   :{c_assembly}>
             <##-   Fixup :{c_raw_assembly}>
             <##-   Encode:{c_encoding}>
             <##-   Size  :{c_encoding_len}>
            <~N~OPs padding until next instruction boundary:{c_opt_padding}>
            <Save ~o~riginal instructions in IDA comment:{c_opt_comment}>{c_opt_chk}>
            """, {
            'c_endian': self.DropdownListControl(
                          items = self.kp_asm.endian_lists.keys(),
                          readonly = True,
                          selval = self.endian_id),
            'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_orig_assembly': self.StringInput(value=self.orig_asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_orig_encoding': self.StringInput(value=self.orig_encoding[:MAX_ENCODING_LEN], width=MAX_ENCODING_LEN),
            'c_orig_len': self.NumericInput(value=self.orig_len, swidth=8, tp=self.FT_DEC),
            'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN),
            'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN),
            'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC),
            'c_syntax': self.DropdownListControl(
                          items = self.syntax_keys,
                          readonly = True,
                          selval = self.syntax_id),
            'c_opt_chk':self.ChkGroupControl(('c_opt_padding', 'c_opt_comment', ''), value=opts['c_opt_chk']),
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        # make some fields read-only in Patch mode
        self.EnableField(self.c_orig_assembly, False)
        self.EnableField(self.c_orig_encoding, False)
        self.EnableField(self.c_orig_len, False)

        return self.update_patchform(fid)


# Search position chooser
class SearchResultChooser(idaapi.Choose2):
    def __init__(self, title, items, flags=0, width=None, height=None, embedded=False, modal=False):
        super(SearchResultChooser, self).__init__(
            title,
            [["Address", idaapi.Choose2.CHCOL_HEX|40]],
            flags = flags,
            width = width,
            height = height,
            embedded = embedded)
        self.n = 0
        self.items = items
        self.selcount = 0
        self.modal = modal

    def OnClose(self):
        return

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

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

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

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


# Search form
class Keypatch_Search(Keypatch_Form):
    def __init__(self, kp_asm, address, assembly=None):
        self.setup(kp_asm, address, assembly)

        # create Search form
        super(Keypatch_Search, self).__init__(
            r"""STARTITEM {id:c_assembly}
BUTTON YES* Search
KEYPATCH:: Search

            {FormChangeCb}
            <A~r~ch       :{c_arch}>
            <E~n~dian     :{c_endian}>
            <~S~yntax     :{c_syntax}>
            <A~d~dress    :{c_addr}>
            <~A~ssembly   :{c_assembly}>
             <##-   Fixup :{c_raw_assembly}>
             <##-   Encode:{c_encoding}>
             <##-   Size  :{c_encoding_len}>
            """, {
            'c_addr': self.NumericInput(value=address, swidth=MAX_ADDRESS_LEN, tp=self.FT_ADDR),
            'c_assembly': self.StringInput(value=self.asm[:MAX_INSTRUCTION_STRLEN], width=MAX_INSTRUCTION_STRLEN),
            'c_raw_assembly': self.StringInput(value='', width=MAX_INSTRUCTION_STRLEN),
            'c_encoding': self.StringInput(value='', width=MAX_ENCODING_LEN),
            'c_encoding_len': self.NumericInput(value=0, swidth=8, tp=self.FT_DEC),
            'c_arch': self.DropdownListControl(
                          items = self.arch_keys,
                          readonly = True,
                          selval = self.arch_id,
                          width = 32),
            'c_endian': self.DropdownListControl(
                          items = self.kp_asm.endian_lists.keys(),
                          readonly = True,
                          selval = self.endian_id),
            'c_syntax': self.DropdownListControl(
                          items = self.syntax_keys,
                          readonly = True,
                          selval = self.syntax_id),
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        # handle the search button
        if fid == -2:
            address = 0
            addresses = []
            while address != idc.BADADDR:
                address = idc.FindBinary(address, idc.SEARCH_DOWN, self.GetControlValue(self.c_encoding))
                if address == idc.BADADDR:
                    break
                addresses.append([address])
                address = address + 1
            c = SearchResultChooser("Searching for [{0}]".format(self.GetControlValue(self.c_raw_assembly)), addresses)
            c.show()
            return 1

        # only Search mode allows to select arch+mode
        arch_id = self.GetControlValue(self.c_arch)
        (arch, mode) = self.kp_asm.get_arch_by_idx(arch_id)

        # assembly is focused
        self.SetFocusedField(self.c_assembly)

        if arch == KS_ARCH_X86:
            # enable Syntax and disable Endian for x86
            self.ShowField(self.c_syntax, True)
            self.EnableField(self.c_syntax, True)
            self.syntax_id = self.GetControlValue(self.c_syntax)
            self.EnableField(self.c_endian, False)
            # set Endian index properly
            self.SetControlValue(self.c_endian, 0)
        elif arch in (KS_ARCH_ARM64, KS_ARCH_HEXAGON, KS_ARCH_SYSTEMZ):
            # no Syntax & Endian option for these archs
            self.ShowField(self.c_syntax, False)
            self.EnableField(self.c_syntax, False)
            self.EnableField(self.c_endian, False)
            # set Endian index properly
            self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0))
        elif (arch == KS_ARCH_PPC) and (mode & KS_MODE_PPC32 != 0):
            # no Syntax & Endian option for these archs
            self.ShowField(self.c_syntax, False)
            self.EnableField(self.c_syntax, False)
            self.EnableField(self.c_endian, False)
            # set Endian index properly
            self.SetControlValue(self.c_endian, (mode & KS_MODE_BIG_ENDIAN != 0))
        else:
            # no Syntax & Endian option
            self.ShowField(self.c_syntax, False)
            self.EnableField(self.c_syntax, False)
            self.EnableField(self.c_endian, True)

        if self.GetControlValue(self.c_endian) == 1:
            endian = KS_MODE_BIG_ENDIAN
        else:
            endian = KS_MODE_LITTLE_ENDIAN

        # update other controls & Encoding with live assembling
        self.update_controls(arch, mode)

        return 1


# About form
class About_Form(idaapi.Form):
    def __init__(self, version):
        # create About form
        super(About_Form, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Keypatch Website
KEYPATCH:: About

            {FormChangeCb}
            Keypatch IDA plugin v%s, using Keystone Engine v%s.
            (c) Nguyen Anh Quynh + Thanh Nguyen, 2018.

            Keypatch is released under the GPL v2.
            Find more info at http://www.keystone-engine.org/keypatch
            """ %(version, keystone.__version__), {
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        if fid == -2:   # Goto homepage
            import webbrowser
            # open Keypatch homepage in a new tab, if possible
            webbrowser.open(KP_HOMEPAGE, new = 2)

        return 1


# Check-for-update form
class Update_Form(idaapi.Form):
    def __init__(self, version, message):
        # create Update form
        super(Update_Form, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Keypatch Website
KEYPATCH:: Check for update

            {FormChangeCb}
            Your Keypatch is v%s
            %s
            """ %(version, message), {
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })
        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        if fid == -2:   # Goto homepage
            import webbrowser
            # open Keypatch homepage in a new tab, if possible
            webbrowser.open(KP_HOMEPAGE, new = 2)

        return 1


try:
    # adapted from pull request #7 by @quangnh89
    class Kp_Menu_Context(idaapi.action_handler_t):

        @classmethod
        def get_name(self):
            return self.__name__

        @classmethod
        def get_label(self):
            return self.label

        @classmethod
        def register(self, plugin, label):
            self.plugin = plugin
            self.label = label
            instance = self()
            return idaapi.register_action(idaapi.action_desc_t(
                self.get_name(),  # Name. Acts as an ID. Must be unique.
                instance.get_label(),  # Label. That's what users see.
                instance  # Handler. Called when activated, and for updating
            ))

        @classmethod
        def unregister(self):
            """Unregister the action.
            After unregistering the class cannot be used.
            """
            idaapi.unregister_action(self.get_name())

        @classmethod
        def activate(self, ctx):
            # dummy method
            return 1

        @classmethod
        def update(self, ctx):
            try:
                if ctx.form_type == idaapi.BWN_DISASM:
                    return idaapi.AST_ENABLE_FOR_FORM
                else:
                    return idaapi.AST_DISABLE_FOR_FORM
            except:
                # Add exception for main menu on >= IDA 7.0
                return idaapi.AST_ENABLE_ALWAYS
            
    # context menu for Patcher
    class Kp_MC_Patcher(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.patcher()
            return 1

    # context menu for Fill Range
    class Kp_MC_Fill_Range(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.fill_range()
            return 1

    # context menu for Undo
    class Kp_MC_Undo(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.undo()
            return 1

    # context menu for Search
    class Kp_MC_Search(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.search()
            return 1

    # context menu for Check Update
    class Kp_MC_Updater(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.updater()
            return 1

    # context menu for About
    class Kp_MC_About(Kp_Menu_Context):
        def activate(self, ctx):
            self.plugin.about()
            return 1
except:
    pass


# hooks for popup menu
class Hooks(idaapi.UI_Hooks):
    if idaapi.IDA_SDK_VERSION >= 700:
        # IDA >= 700 right click widget popup
        def finish_populating_widget_popup(self, form, popup):
            if idaapi.get_widget_type(form) == idaapi.BWN_DISASM:
                try:
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/')
                except:
                    pass
    else:
        # IDA < 700 right click popup
        def finish_populating_tform_popup(self, form, popup):
            # We'll add our action to all "IDA View-*"s.
            # If we wanted to add it only to "IDA View-A", we could
            # also discriminate on the widget's title:
            #
            #  if idaapi.get_tform_title(form) == "IDA View-A":
            #      ...
            #
            if idaapi.get_tform_type(form) == idaapi.BWN_DISASM:
                try:
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Patcher.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Fill_Range.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Undo.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Search.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, "-", 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_Updater.get_name(), 'Keypatch/')
                    idaapi.attach_action_to_popup(form, popup, Kp_MC_About.get_name(), 'Keypatch/')
                except:
                    pass


# check if we already initialized Keypatch
kp_initialized = False

#--------------------------------------------------------------------------
# Plugin
#--------------------------------------------------------------------------
class Keypatch_Plugin_t(idaapi.plugin_t):
    comment = "Keypatch plugin for IDA Pro (using Keystone framework)"
    help = "Find more information on Keypatch at http://keystone-engine.org/keypatch"
    wanted_name = "Keypatch Patcher"
    wanted_hotkey = "Ctrl-Alt-K"
    flags = idaapi.PLUGIN_KEEP

    def load_configuration(self):
        # default
        self.opts = {}

        # load configuration from file
        try:
            f = open(KP_CFGFILE, "rt")
            self.opts = json.load(f)
            f.close()
        except IOError:
            print("Keypatch: FAILED to load config file. Use default setup now.")
        except Exception as e:
            print("Keypatch: FAILED to load config file, with exception: {0}".format(str(e)))

        # use default values if not defined in config file
        if 'c_opt_padding' not in self.opts:
            self.opts['c_opt_padding'] = 1

        if 'c_opt_comment' not in self.opts:
            self.opts['c_opt_comment'] = 2

        self.opts['c_opt_chk'] = self.opts['c_opt_padding'] | self.opts['c_opt_comment']

    def init(self):
        global kp_initialized

        # register popup menu handlers
        try:
            Kp_MC_Patcher.register(self, "Patcher    (Ctrl-Alt-K)")
            Kp_MC_Fill_Range.register(self, "Fill Range")
            Kp_MC_Undo.register(self, "Undo last patching")
            Kp_MC_Search.register(self, "Search")
            Kp_MC_Updater.register(self, "Check for update")
            Kp_MC_About.register(self, "About")
        except:
            pass

        # setup popup menu
        self.hooks = Hooks()
        self.hooks.hook()

        self.opts = None
        if kp_initialized == False:
            kp_initialized = True

            if idaapi.IDA_SDK_VERSION >= 700:
                # Add menu IDA >= 7.0
                idaapi.attach_action_to_menu("Edit/Keypatch/Patcher", Kp_MC_Patcher.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/About", Kp_MC_About.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Check for update", Kp_MC_Updater.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Search", Kp_MC_Search.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Undo last patching", Kp_MC_Undo.get_name(), idaapi.SETMENU_APP)
                idaapi.attach_action_to_menu("Edit/Keypatch/Fill Range", Kp_MC_Fill_Range.get_name(), idaapi.SETMENU_APP)
            else:
                # add Keypatch menu
                menu = idaapi.add_menu_item("Edit/Keypatch/", "Patcher     (Ctrl-Alt-K)", "", 1, self.patcher, None)
                if menu is not None:
                    idaapi.add_menu_item("Edit/Keypatch/", "About", "", 1, self.about, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Check for update", "", 1, self.updater, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Search", "", 1, self.search, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "-", "", 1, self.menu_null, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Undo last patching", "", 1, self.undo, None)
                    idaapi.add_menu_item("Edit/Keypatch/", "Fill Range", "", 1, self.fill_range, None)
                elif idaapi.IDA_SDK_VERSION < 680:
                    # older IDAPython (such as in IDAPro 6.6) does add new submenu.
                    # in this case, put Keypatch menu in menu Edit \ Patch program
                    # not sure about v6.7, so to be safe we just check against v6.8
                    idaapi.add_menu_item("Edit/Patch program/", "-", "", 0, self.menu_null, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: About", "", 0, self.about, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Check for update", "", 0, self.updater, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Search", "", 0, self.search, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Undo last patching", "", 0, self.undo, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Fill Range", "", 0, self.fill_range, None)
                    idaapi.add_menu_item("Edit/Patch program/", "Keypatch:: Patcher     (Ctrl-Alt-K)", "", 0, self.patcher, None)

            print("=" * 80)
            print("Keypatch v{0} (c) Nguyen Anh Quynh & Thanh Nguyen, 2018".format(VERSION))
            print("Keypatch is using Keystone v{0}".format(keystone.__version__))
            print("Keypatch Patcher's shortcut key is Ctrl-Alt-K")
            print("Use the same hotkey Ctrl-Alt-K to open 'Fill Range' window on a selected range of code")
            print("To revert (undo) the last patching, choose menu Edit | Keypatch | Undo last patching")
            print("Keypatch Search is available from menu Edit | Keypatch | Search")
            print("Find more information about Keypatch at http://keystone-engine.org/keypatch")

            self.load_configuration()

            print("=" * 80)
            self.kp_asm = Keypatch_Asm()

        return idaapi.PLUGIN_KEEP

    def term(self):
        if self.hooks is not None:
            self.hooks.unhook()
            self.hooks = None

        if self.opts is None:
            return
        #save configuration to file
        try:
            json.dump(self.opts, open(KP_CFGFILE, "wt"))
        except Exception as e:
            print("Keypatch: FAILED to save config file, with exception: {0}".format(str(e)))
        else:
            print("Keypatch: configuration is saved to {0}".format(KP_CFGFILE))

    # null handler
    def menu_null(self):
        pass

    # handler for About menu
    def about(self):
        f = About_Form(VERSION)
        f.Execute()
        f.Free()

    # handler for Check-for-Update menu
    def updater(self):
        (r, content) = url_download(KP_GITHUB_VERSION)
        if r == 0:
            # find stable version
            sig = 'VERSION_STABLE = "'
            tmp = content[content.find(sig)+len(sig):]
            version_stable = tmp[:tmp.find('"')]

            # compare with the current version
            if version_stable == VERSION:
                f = Update_Form(VERSION, "Good, you are already on the latest stable version!")
                f.Execute()
                # free this form
                f.Free()
            else:
                f = Update_Form(VERSION, "Download latest stable version {0} from http://keystone-engine.org/keypatch".format(version_stable))
                f.Execute()
                # free this form
                f.Free()
        else:
            # fail to download
            idc.Warning("ERROR: Keypatch failed to connect to internet (Github). Try again later.")
            print("Keypatch: FAILED to connect to Github to check for latest update. Try again later.")

    # handler for Undo menu
    def undo(self):
        global patch_info
        if len(patch_info) == 0:
            # TODO: disable Undo menu?
            idc.Warning("ERROR: Keypatch already got to the last undo patching!")
        else:
            (address, assembly, p_orig_data, patch_comment) = patch_info[-1]

            # undo the patch
            self.kp_asm.patch_code(address, None, None, None, None, orig_asm=[assembly], patch_data=p_orig_data, patch_comment=patch_comment, undo=True)
            del(patch_info[-1])

    # handler for Search menu
    def search(self):
        address = idc.ScreenEA()
        f = Keypatch_Search(self.kp_asm, address)
        f.Execute()
        f.Free()

    # handler for Patcher menu
    def patcher(self):
        # be sure that this arch is supported by Keystone
        if self.kp_asm.arch is None:
            idc.Warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!")
            return

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

        address = idc.ScreenEA()

        if self.opts is None:
            self.load_configuration()

        init_assembly = None
        while True:
            f = Keypatch_Patcher(self.kp_asm, address, assembly=init_assembly, opts=self.opts)
            ok = f.Execute()
            if ok == 1:
                try:
                    syntax = None
                    if f.kp_asm.arch == KS_ARCH_X86:
                        syntax_id = f.c_syntax.value
                        syntax = self.kp_asm.get_syntax_by_idx(syntax_id)

                    assembly = f.c_assembly.value
                    self.opts = f.get_opts('c_opt_chk')
                    padding = (self.opts.get("c_opt_padding", 0) != 0)
                    comment = (self.opts.get("c_opt_comment", 0) != 0)

                    raw_assembly = self.kp_asm.ida_resolve(assembly, address)

                    print("Keypatch: attempting to modify \"{0}\" at 0x{1:X} to \"{2}\"".format(
                            self.kp_asm.ida_get_disasm(address), address, assembly))

                    length = self.kp_asm.patch_code(address, raw_assembly, syntax, padding, comment, None)
                    if length > 0:
                        # update start address pointing to the next instruction
                        init_assembly = None
                        address += length
                    else:
                        init_assembly = f.c_assembly.value
                        if length == 0:
                            idc.Warning("ERROR: Keypatch found invalid assembly [{0}]".format(assembly))
                        elif length == -1:
                            idc.Warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(address))
                        elif length == -2:
                            idc.Warning("ERROR: Keypatch can't read original data at 0x{0:X}, try again".format(address))

                except KsError as e:
                    print("Keypatch Error: {0}".format(e))
            else:   # Cancel
                break
            f.Free()

    # handler for Fill Range menu
    def fill_range(self):
        # be sure that this arch is supported by Keystone
        if self.kp_asm.arch is None:
            idc.Warning("ERROR: Keypatch cannot handle this architecture (unsupported by Keystone), quit!")
            return
               
        selection, addr_begin, addr_end = idaapi.read_selection()
        if not selection:
            idc.Warning("ERROR: Keypatch requires a range to be selected for fill in, try again")
            return

        if self.opts is None:
            self.load_configuration()

        init_assembly = None
        f = Keypatch_FillRange(self.kp_asm, addr_begin, addr_end, assembly=init_assembly, opts=self.opts)
        ok = f.Execute()
        if ok == 1:
            try:
                syntax = None
                if f.kp_asm.arch == KS_ARCH_X86:
                    syntax_id = f.c_syntax.value
                    syntax = self.kp_asm.get_syntax_by_idx(syntax_id)

                assembly = f.c_assembly.value
                self.opts = f.get_opts('c_opt_chk')
                padding = (self.opts.get("c_opt_padding", 0) != 0)
                comment = (self.opts.get("c_opt_comment", 0) != 0)

                raw_assembly = self.kp_asm.ida_resolve(assembly, addr_begin)

                print("Keypatch: attempting to fill range [0x{0:X}:0x{1:X}] with \"{2}\"".format(
                    addr_begin, addr_end - 1, assembly))

                length = self.kp_asm.fill_code(addr_begin, addr_end, raw_assembly, syntax, padding, comment, None)
                if length == 0:
                    idc.Warning("ERROR: Keypatch failed to process this input.")
                    print("Keypatch: FAILED to process this input '{0}'".format(assembly))
                elif length == -1:
                    idc.Warning("ERROR: Keypatch failed to patch binary at 0x{0:X}!".format(addr_begin))

            except KsError as e:
                print("Keypatch Error: {0}".format(e))

        # free this form
        f.Free()

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


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


repository: JsHuang/IDATools
file: Scripts/ida rop/idarop_plugin_t.py
description: A repo of collected useful IDA scripts and plugins
wanted_name: IDA ROP
created_at: 2021-10-26 05:00:34+00:00
pushed_at: 2024-06-25 08:08:01+00:00
url:
https://github.com/JsHuang/IDATools/blob/b1c898e4f0bd5ee9ecfa7633673e785211389434/Scripts/ida%20rop/idarop_plugin_t.py

content:
# IDA libraries
import idaapi
from idaapi import plugin_t

from idarop import IDAROP_VERSION, IDAROP_DESCRIPTION
from idarop.ui import IdaRopManager

class idarop_t(plugin_t):

    flags = idaapi.PLUGIN_UNL
    comment = IDAROP_DESCRIPTION
    help = IDAROP_DESCRIPTION
    wanted_name = "IDA ROP"
    wanted_hotkey = ""

    def init(self):
        """ On script initalisation : load previous rop results and init menu items """

        # Only Intel x86/x86-64 are supported
        if idaapi.ph_get_id() == idaapi.PLFM_386:

            global idarop_manager

            # Check if already initialized
            if not 'idarop_manager' in globals():

                idarop_manager = IdaRopManager()
                if idarop_manager.add_menu_items():
                    print("[IdaRop] Failed to initialize IDA Sploiter.")
                    idarop_manager.del_menu_items()
                    del idarop_manager
                    return idaapi.PLUGIN_SKIP
                else:
                    try:
                        idarop_manager.load_internal_db()
                    except Exception as e:
                        pass

                    print("[IdaRop] IDA ROP View  v%s initialized " % IDAROP_VERSION)

            return idaapi.PLUGIN_KEEP
        else:
            return idaapi.PLUGIN_SKIP

    def run(self, arg):
        pass

    def term(self):
        """ On IDA's close event, export the Rop gadget list in a default csv file"""
        idaapi.show_wait_box("Saving gadgets ...")
        try:
            idarop_manager.save_internal_db()
        except Exception as e:
            pass
        idaapi.hide_wait_box()

def PLUGIN_ENTRY():
    return idarop_t()

###############################################################################
# Script / Testing
###############################################################################

def idarop_main():
    global idarop_manager

    if 'idarop_manager' in globals():
        idarop_manager.del_menu_items()
        del idarop_manager

    idarop_manager = IdaRopManager()
    idarop_manager.add_menu_items()

    idarop = idarop_manager.idarop

if __name__ == '__main__':
    #idarop_main()
    pass


repository: Asuri-Team/NUAACTF2022-Challenges
file: pwn/ezmmio/source_code/qiling-1.4.4/qiling/extensions/idaplugin/qilingida.py
description: NUAACTF 2022 challenges source code
wanted_name: Qiling Emulator
created_at: 2022-12-05 02:59:01+00:00
pushed_at: 2023-01-31 10:11:33+00:00
url:
https://github.com/Asuri-Team/NUAACTF2022-Challenges/blob/969b4028ef5f04498b2a49bd53ea52b820f3eb71/pwn/ezmmio/source_code/qiling-1.4.4/qiling/extensions/idaplugin/qilingida.py

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

UseAsScript = True

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

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

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


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

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

class IDA:
    def __init__(self):
        pass

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

### View Class

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

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

        self.menu_update = 1

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

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

                def update(self, ctx):
                    return AST_ENABLE_ALWAYS

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

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

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

        return True

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

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

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

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

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

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

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

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

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

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

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

            self.AddLine(COLSTR(line, clr))

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

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

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

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

        if ql is None:
            return

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

        size = len(memory)

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

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

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

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

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

        self.Refresh()
        self.lastContent = memory

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

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

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

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

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

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

        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        if fid == -2:   # Goto homepage
            import webbrowser
            # open Keypatch homepage in a new tab, if possible
            webbrowser.open(QilingHomePage, new = 2)

        return 1

class QlEmuUpdateDialog(Form):
    def __init__(self, version, message):
        super(QlEmuUpdateDialog, self).__init__(
            r"""STARTITEM 0
BUTTON YES* Open Qiling Website
Qiling:: Check for update
            {FormChangeCb}
            Your Qiling is v%s
            %s
            """ %(version, message), {
            'FormChangeCb': self.FormChangeCb(self.OnFormChange),
            })
        self.Compile()

    # callback to be executed when any form control changed
    def OnFormChange(self, fid):
        if fid == -2:   # Goto homepage
            import webbrowser
            # open Keypatch homepage in a new tab, if possible
            webbrowser.open(QilingHomePage, new = 2)

        return 1

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

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

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

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

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

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

    def OnClose(self):
        pass

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

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

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

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

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

    @staticmethod
    def url_download(url):
        try:
            from urllib2 import Request, urlopen, URLError, HTTPError
        except:
            from urllib.request import Request, urlopen
            from urllib.error import URLError, HTTPError

        # create the url and the request
        req = Request(url)

        # Open the url
        try:
            # download this URL
            f = urlopen(req)
            content = f.read()
            return (0, content)

        # handle errors
        except HTTPError as e:
            # print "HTTP Error:", e.code , url
            # fail to download this file
            return (1, None)
        except URLError as e:
            # print "URL Error:", e.reason , url
            # fail to download this file
            return (1, None)
        except Exception as e:
            # fail to save the downloaded file
            # print("Error:", e)
            return (2, None)

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

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

        def flush(self):
            pass

        def isatty(self):
            return False

### Qiling wrapper

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

    def start(self, *args, **kwargs):
        self.ql = Qiling(argv=self.path, rootfs=self.rootfs, verbose=QL_VERBOSE.DEBUG, env=self.env, log_plain=True, *args, **kwargs)

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

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

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

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

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

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

        savepath = savedlg.path_name.value

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

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

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

        loadname = loaddlg.file_name.value

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

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

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

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

### Plugin

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

    popup_menu_hook = None

    flags = PLUGIN_HIDE
    comment = ""

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

    ### View Data

    qlemuregview = None
    qlemustackview = None
    qlemumemview = {}

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

    ### Main Framework

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

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

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

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

    ### Actions

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

        ida_ida.ql_plugin = self

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def ql_menu_null(self):
        pass

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return True

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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


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

    ### Hook

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

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

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

            self.is_change_addr += 1


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

    ### User Scripts

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

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

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

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

    ### Dialog

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

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

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

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

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

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

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

    ### Menu

    menuitems = []

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

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

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

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

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

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

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

    ### POPUP MENU

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

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

    # IDA 7.x

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

    ### Close View

    def ql_close_reg_view(self):
        self.qlemuregview = None

    def ql_close_stack_view(self):
        self.qlemustackview = None

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

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

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

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

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

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


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

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


