# plugins

repository: hoangcuongflp/Stuff
file: ida-master/plugins/codatify/codatify.py
description: because I've orderliness obsession :(
wanted_name: Define all data and code
created_at: 2015-10-12 16:35:32+00:00
pushed_at: 2014-12-22 00:32:06+00:00
url:
https://github.com/hoangcuongflp/Stuff/blob/ee100760a05f524ff73837222d80f1dd12da3cee/ida-master/plugins/codatify/codatify.py

content:
# IDA plugin that converts all data in data segments to defined data types, and all data in code segments to code.
#
# Use by going to Options->Define data and code, or use the Alt+3 hotkey.
#
# Craig Heffner
# Tactical Network Solutions

import idc
import idaapi
import idautils

class Codatify(object):

    CODE = 2
    DATA = 3
    SEARCH_DEPTH = 25

    def __init__(self):
        pass

    # Get the start of the specified segment type (2 == code, 3 == data)
    def get_start_ea(self, attr):
        ea = idc.BADADDR
        seg = idc.FirstSeg()

        while seg != idc.BADADDR:
            if idc.GetSegmentAttr(seg, idc.SEGATTR_TYPE) == attr:
                ea = seg
                break
            else:
                seg = idc.NextSeg(seg)
    
        return ea

    # Creates ASCII strings
    def stringify(self):
        n = 0
        ea = self.get_start_ea(self.DATA)

        if ea == idc.BADADDR:
            ea = idc.FirstSeg()

        print ("Looking for possible strings starting at: %s:0x%X..." % (idc.SegName(ea), ea)),

        for s in idautils.Strings():
            if s.ea > ea:
                if not idc.isASCII(idc.GetFlags(s.ea)) and idc.MakeStr(s.ea, idc.BADADDR):
                    n += 1

        print "created %d new ASCII strings" % n

    # Converts remaining data into DWORDS.
    def datify(self):
        ea = self.get_start_ea(self.DATA)
        if ea == idc.BADADDR:
            ea = idc.FirstSeg()
        
        print "Converting remaining data to DWORDs...",
    
        while ea != idc.BADADDR:
            flags = idc.GetFlags(ea)
                    
            if idc.isUnknown(flags) or idc.isByte(flags):
                idc.MakeDword(ea)
                idc.OpOff(ea, 0, 0)

            ea = idc.NextAddr(ea)

        print "done."

    def pointify(self):
        counter = 0

        print "Renaming pointers...",

        for (name_ea, name) in idautils.Names():
            for xref in idautils.XrefsTo(name_ea):
                xref_name = idc.Name(xref.frm)
                if xref_name and xref_name.startswith("off_"):
                    i = 0
                    new_name = name + "_ptr"
                    while idc.LocByName(new_name) != idc.BADADDR:
                        new_name = name + "_ptr%d" % i
                        i += 1

                    if idc.MakeName(xref.frm, new_name):
                        counter += 1
                    #else:
                    #    print "Failed to create name '%s'!" % new_name

        print "renamed %d pointers" % counter

    # Creates functions and code blocks
    def codeify(self, ea=idc.BADADDR):
        func_count = 0
        code_count = 0

        if ea == idc.BADADDR:
            ea = self.get_start_ea(self.CODE)
            if ea == idc.BADADDR:
                ea = idc.FirstSeg()

        print "\nLooking for undefined code starting at: %s:0x%X" % (idc.SegName(ea), ea)

        if self.get_start_ea(self.DATA) == idc.BADADDR:
            print "WARNING: No data segments defined! I don't know where the code segment ends and the data segment begins."
    

        while ea != idc.BADADDR:
            try:
                if idc.GetSegmentAttr(ea, idc.SEGATTR_TYPE) == self.CODE:
                    if idc.GetFunctionName(ea) != '':
                        ea = idc.FindFuncEnd(ea)
                        continue
                    else:
                        if idc.MakeFunction(ea):
                            func_count += 1
                        elif idc.MakeCode(ea):
                            code_count += 1
            except:
                pass
            
            ea = idc.NextAddr(ea)
    
        print "Created %d new functions and %d new code blocks\n" % (func_count, code_count)



class codatify_t(idaapi.plugin_t):
    flags = 0
    comment = ""
    help = ""
    wanted_name = "Define all data and code"
    wanted_hotkey = ""

    def init(self):
        self.menu_context = idaapi.add_menu_item("Options/", "Fixup code", "Alt-3", 0, self.fix_code, (None,))
        self.menu_context = idaapi.add_menu_item("Options/", "Fixup data", "Alt-4", 0, self.fix_data, (None,))
        return idaapi.PLUGIN_KEEP

    def term(self):
        idaapi.del_menu_item(self.menu_context)
        return None

    def run(self, arg):
        pass

    def fix_code(self, arg):
        cd = Codatify()
        cd.codeify()

    def fix_data(self, arg):
        cd = Codatify()
        cd.stringify()
        cd.datify()
        cd.pointify()

def PLUGIN_ENTRY():
    return codatify_t()


repository: WPeace-HcH/ElfDumper
file: ElfDumper.py
description: A plugin for IDA that can dump the ELF file easily.
url: https://github.com/WPeace-HcH/ElfDumper/blob/e1a9b0f9c24f6c84a9bfecb0546cf37440062637/ElfDumper.py
wanted_name: ElfDumper
comment: ElfDumper Plugin for IDA
created_at: 2023-01-04 08:07:48+00:00
pushed_at: 2023-04-03 10:10:42+00:00
content:
import idc
import idaapi
import sys, os
from idaapi import Form
path = os.path.dirname(os.path.abspath(__file__)) + "\\ElfDumper_WPeace\\"
sys.path.append(path)
import DumpELF_x86
import DumpELF_x64


class myplugin_elfdumper(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = "ElfDumper Plugin for IDA"
    help = "Find more information at https://github.com/wpeace-hch"
    wanted_name = "ElfDumper"
    wanted_hotkey = "Ctrl-Alt-D"
    def __init__(self):
        idaapi.action_handler_t.__init__(self)
    
    def init(self):
        print("\nElfDumper By WPeace.")
        try:
            WPe_Patcher_elfdumper.register(self, "ElfDumper    (Ctrl-Alt-D)")
        except:
            pass
        if idaapi.IDA_SDK_VERSION >= 740:
            idaapi.attach_action_to_menu("Edit/WPeace_Plugins/ElfDumper    (Ctrl-Alt-D)", WPe_Patcher_elfdumper.get_name(), idaapi.SETMENU_APP)
        else:
            print("Your IDA version needs to be greater than 7.4! :(@WPeace")
        return idaapi.PLUGIN_OK
    
    def run(self, arg):
        elfdumper_main()
    
    def term(self):
        print("ElfDumper v2.0 works fine! :)@WPeace\n")


class 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(),
            instance.get_label(),
            instance
        ))
        
    @classmethod
    def unregister(self):
        idaapi.unregister_action(self.get_name())
        
    @classmethod
    def activate(self, ctx):
        return 1
        
    @classmethod
    def update(self, ctx):
        try:
            return idaapi.AST_ENABLE_FOR_WIDGET
        except Exception as e:
            return idaapi.AST_ENABLE_ALWAYS


class WPe_Patcher_elfdumper(Menu_Context):
    def activate(self, ctx):
        elfdumper_main()
        return 1


class SuccessForm_x32(Form):
    def __init__(self):
        self.invert = False
        Form.__init__(self, r"""STARTITEM 0
Success

{FormChangeCb}
Dump x32 ELF finished
OutputFile = dumpELFfile.dex
      
""", {
            'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
        })
    
    def OnFormChange(self, fid):
        return 1


class SuccessForm_x64(Form):
    def __init__(self):
        self.invert = False
        Form.__init__(self, r"""STARTITEM 0
Success

{FormChangeCb}
Dump x64 ELF finished
OutputFile = dumpELFfile.dex
      
""", {
            'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
        })
    
    def OnFormChange(self, fid):
        return 1


class ErrorForm(Form):
    def __init__(self):
        self.invert = False
        Form.__init__(self, r"""STARTITEM 0
Error

{FormChangeCb}
请输入正确的Dump ELF的地址！
      
""", {
            'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
        })
    
    def OnFormChange(self, fid):
        return 1


class MyForm(Form):
    def __init__(self):
        self.invert = False
        Form.__init__(self, r"""STARTITEM 0
ElfDumper

{FormChangeCb}
输入Dump ELF的地址：
<##Hex Addr:{InputStr}>
<##Start to Dump:{Button0}>
""", {
            'InputStr': Form.StringInput(swidth=10, value="0x"),
            'Button0': Form.ButtonInput(self.OnButton0),
            'FormChangeCb': Form.FormChangeCb(self.OnFormChange),
        })

    def OnButton0(self, code=0):
        errorFlag = 0
        e_ident_bit = 0
        if is_valid_hex(self.GetControlValue(self.InputStr)):
            hexAddr = int(self.GetControlValue(self.InputStr), 16)
            elf_magic = idc.get_wide_dword(hexAddr)
            e_ident_bit = idc.get_wide_byte(hexAddr + 0x4)
            if elf_magic == 0x464c457f or elf_magic == 0x7f454c46:
                print("\nFind the ELF, start to dump. @WPeace")
                if e_ident_bit == 1:
                    DumpELF_x86.main(hexAddr)
                    f_success = SuccessForm_x32()
                    f_success.Compile()
                    f_success.Execute()
                    f_success.Free()
                elif e_ident_bit == 2:
                    DumpELF_x64.main(hexAddr)
                    f_success = SuccessForm_x64()
                    f_success.Compile()
                    f_success.Execute()
                    f_success.Free()
                else:
                    print("Can't recognise the e_ident_bit. @WPeace")
                    errorFlag = 1
            else:
                errorFlag = 1
        else:
            errorFlag = 1
        if errorFlag == 1:
            f_error = ErrorForm()
            f_error.Compile()
            f_error.Execute()
            f_error.Free()  
        
    def OnFormChange(self, fid):
        return 1


def is_valid_hex(str):
    try:
        int(str, 16)
        return True
    except ValueError:
        return False


def elfdumper_main():
    global f
    f = MyForm()
    f.Compile()
    ok = f.Execute()
    f.Free()


def PLUGIN_ENTRY():
    return myplugin_elfdumper()


repository: ramikg/search-from-ida
file: search_from_ida.py
description: Look up highlighted IDA strings using your browser
created_at: 2022-04-02 15:04:27+00:00
pushed_at: 2023-02-21 14:30:08+00:00
url:
https://github.com/ramikg/search-from-ida/blob/07b6dadabd49975c1546df92a6457ecbdecf1025/search_from_ida.py

content:
import os
import webbrowser
try:  # Python 3
    from urllib.parse import urlparse, quote
except ImportError:  # Python 2
    from urlparse import urlparse
    from urllib import quote

import ida_idaapi
import ida_kernwin

QUERY_URL_FORMAT = 'https://google.com/search?q="{}"'

PLUGIN_NAME = ACTION_NAME = 'Search from IDA'
ACTION_LABEL_FORMAT = 'Search for "{}"'
ACTION_HOTKEY = 'Ctrl-Shift-Q'
ICONS_DIRECTORY_NAME = 'icons'
ICON_FILE_EXTENSION = '.png'
SUPPORTED_BUILTIN_WINDOWS = (ida_kernwin.BWN_DISASM, ida_kernwin.BWN_PSEUDOCODE)


def _get_highlight(widget):
    highlight = ida_kernwin.get_highlight(widget)
    if highlight:
        return highlight[0]
    else:
        return ''


def _should_enable_action(widget):
    if ida_kernwin.get_widget_type(widget) not in SUPPORTED_BUILTIN_WINDOWS:
        return False
    if not _get_highlight(widget):
        return False
    return True


class SearchHandler(ida_kernwin.action_handler_t):
    def activate(self, ctx):
        quoted_highlight = quote(_get_highlight(ctx.widget))
        webbrowser.open_new_tab(QUERY_URL_FORMAT.format(quoted_highlight))

    def update(self, ctx):
        if _should_enable_action(ctx.widget):
            return ida_kernwin.AST_ENABLE
        else:
            return ida_kernwin.AST_DISABLE


class ContextMenuHooks(ida_kernwin.UI_Hooks):
    def finish_populating_widget_popup(self, widget, popup_handle):
        if _should_enable_action(widget):
            highlight = _get_highlight(widget)
            ida_kernwin.update_action_label(ACTION_NAME, ACTION_LABEL_FORMAT.format(highlight))
            ida_kernwin.attach_action_to_popup(widget, popup_handle, ACTION_NAME, None)


class SearchFromIda(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    comment = PLUGIN_NAME
    help = ''
    wanted_name = PLUGIN_NAME
    wanted_hotkey = ''

    _hooks = ContextMenuHooks()

    @staticmethod
    def _get_icon_path():
        fqdn = urlparse(QUERY_URL_FORMAT).netloc
        icon_filename = os.path.join(ICONS_DIRECTORY_NAME, fqdn + ICON_FILE_EXTENSION)
        icon_path = os.path.join(os.path.dirname(__file__), icon_filename)
        return icon_path

    def _update_action_icon(self):
        """
        If you think this function is awkward, you are not mistaken.
        This helps circumventing certain bugs in the icons API.
        """
        icon_path = self._get_icon_path()
        if not os.path.exists(icon_path):
            print('[{}] File {} does not exist.'.format(PLUGIN_NAME, icon_path))
            self._action_icon = 0
        else:
            self._action_icon = ida_kernwin.load_custom_icon(icon_path)
            if self._action_icon:
                ida_kernwin.update_action_icon(ACTION_NAME, self._action_icon)

    def init(self):
        action_desc = ida_kernwin.action_desc_t(ACTION_NAME,
                                                ACTION_NAME,
                                                SearchHandler(),
                                                ACTION_HOTKEY)

        ida_kernwin.register_action(action_desc)
        self._update_action_icon()

        self._hooks.hook()

        return ida_idaapi.PLUGIN_KEEP

    def run(self):
        pass

    def term(self):
        self._hooks.unhook()
        if self._action_icon:
            ida_kernwin.free_custom_icon(self._action_icon)


def PLUGIN_ENTRY():
    return SearchFromIda()


repository: o0Starley0o/HexRaysPlugins
file: logger.py
wanted_name: Renamer
comment: This is a comment
created_at: 2019-09-03 20:45:11+00:00
pushed_at: 2019-09-09 20:32:59+00:00
url:
https://github.com/o0Starley0o/HexRaysPlugins/blob/62aeb09a647d269ed01e1936d141dac2c1059124/logger.py

content:
# import sys
# import os
#
# current_path = os.path.dirname(__file__)
#
# egg_loc = os.path.join(current_path, "pycharm-debug.egg")
# sys.path.append(egg_loc)
# print egg_loc
# import pydevd


import PyQt5.QtCore as QtCore
import PyQt5.QtWidgets as QtWidgets
import idaapi
import idautils
import idc
function_list = ["sub_401500", "sub_401560"]


class FunctionList(idaapi.PluginForm):
    def __init__(self):
        super(FunctionList, self).__init__()
        self.parent = None
        self.table = QtWidgets.QTableWidget()
        self.le = QtWidgets.QLineEdit()
        self.le.move(130, 22)
        self.main_data = []



    def OnCreate(self, form):
        self.parent = idaapi.PluginForm.FormToPyQtWidget(form)
        self.parent.setStyleSheet(
            "QTableWidget {background-color: transparent; selection-background-color: #87bdd8;}"
            "QHeaderView::section {background-color: transparent; border: 0.7px solid;}"
            "QPushButton {width: 50px; height: 20px;}"
        )
        self.parent.resize(400, 600)
        self.parent.setWindowTitle('Renamer')

        self.table.setStyleSheet(
            "QHeaderView::section{Background-color:rgb(190,1,1); border - radius:14px;}"
            "QTableWidget {background-color: #FFFFFF; selection-background-color: #000000;}"
        )
        self.table.setColumnCount(3)
        self.table.setRowCount(1)
        self.table.setHorizontalHeaderLabels(["LOG FUNCTION", "CALL", "POTENTIAL NAME"])
        self.table.verticalHeader().hide()
        self.table.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
        self.table.cellDoubleClicked.connect(self.cellDoubleClicked)

        btn_ADD = QtWidgets.QPushButton("&ADD")
        btn_RENAME = QtWidgets.QPushButton("&RENAME")
        btn_SHOW = QtWidgets.QPushButton("&SHOW")

        grid_box = QtWidgets.QGridLayout()
        grid_box.setSpacing(0)
        grid_box.addWidget(btn_ADD, 0, 0)
        grid_box.addWidget(btn_SHOW, 0, 1)
        grid_box.addWidget(btn_RENAME, 0, 2)
        grid_box.addItem(QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding), 0, 5)
        grid_box.addItem(QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding), 1, 5)
        vertical_box = QtWidgets.QVBoxLayout()
        vertical_box.addWidget(self.table)
        vertical_box.addLayout(grid_box)
        self.parent.setLayout(vertical_box)

        btn_ADD.clicked.connect(self.buildExamplePopup)
        btn_SHOW.clicked.connect(self.feed)
        btn_RENAME.clicked.connect(self.rename)

    def OnClose(self, form):
        pass

    def feed(self):
        names_of_calls = []
        self.main_data = []
        self.table.setRowCount(0)
        global function_list
        for name in function_list:
            names_of_calls += self.get_callers(str(name))

        for ea in names_of_calls:
            try:
                cfunc = idaapi.decompile(ea)
            except idaapi.DecompilationFailure:
                print "Error decompiling function @ 0x%x" % ea
                cfunc = None

            if cfunc:
                fp = func_parser_t(cfunc)
                fp.apply_to(cfunc.body, None)
                self._make_choser_entry(ea, fp.data)

        self.numrows = len(self.main_data)
        self.numcols = len(self.main_data[0])
        for i in self.main_data:
            if len(i) > self.numcols:
                self.numcols = len(i)

        self.table.setColumnCount(self.numcols+1)
        self.table.setRowCount(self.numrows)
        columns = ["LOG FUNCTION", "CALL"]
        columns += ["arg "+str(i) for i in range(1, self.numcols-1)]
        columns += ["POTENTIAL NAME"]
        self.table.setHorizontalHeaderLabels(columns)


        for row in range(self.numrows):
            for column in range(self.numcols):
                try:
                    self.table.setItem(row, column, QtWidgets.QTableWidgetItem((self.main_data[row][column])))
                    if self.main_data[row][column] != ".":
                        self.table.setItem(row, self.numcols, QtWidgets.QTableWidgetItem((self.main_data[row][column])))
                except:
                    self.table.setItem(row, column, QtWidgets.QTableWidgetItem("."))
                self.table.item(row, column).setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
        return


    def rename (self):
        for row in range(self.numrows):
            if str(self.table.model().index(row, self.numcols).data()) == '.':
                print "Function \"{}\" not renamed".format(str(self.table.model().index(row, 1).data()))
            else:
                func_adress = idaapi.get_name_ea(idaapi.BADADDR, str(self.table.model().index(row, 1).data()))
                idc.MakeName(func_adress, str(self.table.model().index(row, self.numcols).data()))
                print "Function \"{}\" renamed to \"{}\" successfully".format(str(self.table.model().index(row, 1).data()), str(self.table.model().index(row, self.numcols).data()))

    def cellDoubleClicked(self, row, column):
        if (column > 1) & (column < (self.table.columnCount()-1)):
            self.table.setItem(row, self.numcols, QtWidgets.QTableWidgetItem((self.table.model().index(row, column).data())))
        elif column <= 1:
            jumpto(idaapi.get_name_ea(idaapi.BADADDR, str(self.table.model().index(row, column).data())))

    def Show(self, caption=None, options=0):
        return idaapi.PluginForm.Show(self, caption, options=options)

    def buildExamplePopup(self):
        self.exPopup = examplePopup()
        self.exPopup.show()

    def get_callers(self, name):
        for xr in idautils.CodeRefsTo(idaapi.get_name_ea(idaapi.BADADDR, name), True):
            fn = idaapi.get_func(xr)
            if fn:
                yield fn.startEA

    def _make_choser_entry(self, ea, data):
        for item in data:
            full_list = [item.name, idc.get_func_off_str(ea)]
            for i in item.dst:
                full_list += [i]
            self.main_data.append(full_list)
        return



class examplePopup(QtWidgets.QWidget):
    def __init__(self):
        super(examplePopup, self).__init__()

        self.initUI()

    def initUI(self):
        self.resize(250, 250)
        self.center()
        self.setWindowTitle("Enter log func")
        global function_list

        self.lblName = QtWidgets.QPlainTextEdit(self)
        for i in function_list:
            self.lblName.insertPlainText(str(i)+'\n')
        self.btnOK = QtWidgets.QPushButton("OK", self)
        vertical_box = QtWidgets.QVBoxLayout(self)
        vertical_box.addWidget(self.lblName)
        vertical_box.addWidget(self.btnOK)
        self.btnOK.clicked.connect(self.btnOk_clicked)


    def center(self):
        qr = self.frameGeometry()
        cp = QtWidgets.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def btnOk_clicked(self):
        global function_list
        function_list = []
        data = self.lblName.toPlainText()
        function_list += data.split("\n")
        self.close()


class TakeAttributes():
    def __init__(self, ea, name, dst):
        self.ea = ea
        self.name = name
        self.dst = dst


class func_parser_t(idaapi.ctree_visitor_t):
    def __init__(self, cfunc):
        idaapi.ctree_visitor_t.__init__(self, idaapi.CV_FAST)
        self.cfunc = cfunc
        self.data = []
        return

    def _add_func_call(self, func_info):
        self.data.append(func_info)
        return

    def _parse_memcpy(self, e):
        args = e.a

        dst_name = []
        for i in args:
            if idc.GetString(i.obj_ea, -1, idc.ASCSTR_C) is not None:
                dst_name += [idc.GetString(i.obj_ea, -1, idc.ASCSTR_C)]
            else:
                dst_name += ["."]

        name = idaapi.tag_remove(e.x.print1(None))

        self._add_func_call(TakeAttributes(e.ea, name, dst_name))
        return True

    def visit_expr(self, e):
        op = e.op
        if op == idaapi.cot_call:
            name = idaapi.tag_remove(e.x.print1(None))
            if name in function_list:
                self._parse_memcpy(e)
        return 0


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

    help = "This is help"
    wanted_name = "Renamer"
    wanted_hotkey = "Alt-F8"

    def init(self):
        idaapi.load_plugin('hexrays')
        idaapi.msg("init() called!\n")

        return idaapi.PLUGIN_OK

    def run(self, arg):
        #pydevd.settrace('localhost', port=12345, stdoutToServer=True, stderrToServer=True)
        if not idaapi.init_hexrays_plugin():
            print "This script requires the HexRays decompiler plugin."
        else:
            Table = FunctionList()
            Table.Show()


    def term(self):
        return


def PLUGIN_ENTRY():
    return myplugin_t()


repository: KaztoRay/MobilityBinwalker
file: src/scripts/binida.py
wanted_name: Binwalk IDA Plugin
comment: Scan the current IDB for file signatures
created_at: 2024-08-27 17:40:41+00:00
pushed_at: 2024-08-27 17:44:45+00:00
url:
https://github.com/KaztoRay/MobilityBinwalker/blob/0429226a17e2ce0379ba7dd232c2c0d242f0fe8b/src/scripts/binida.py

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

    from idaapi import *
else:
    pass

# 'try'를 사용하여 이전 API와의 호환성을 유지
# 핸들러를 위한 Actions API 사용
try:
    class OpHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)  # 부모 클래스 초기화

        def activate(self, ctx):
            # opcode_scan을 실행하여 IDB 파일에서 명령어(opcode)를 검색
            arg = None
            a = binwalk_t()
            a.opcode_scan(arg)
            return 1

        def update(self, ctx):
            # 메뉴 항목을 항상 활성화된 상태로 유지
            return idaapi.AST_ENABLE_ALWAYS
except AttributeError:
    pass

# 'try'를 사용하여 이전 API와의 호환성을 유지
# 핸들러를 위한 Actions API 사용
try:
    class SigHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)  # 부모 클래스 초기화

        def activate(self, ctx):
            # signature_scan을 실행하여 IDB 파일에서 파일 서명을 검색
            arg = None
            b = binwalk_t()
            b.signature_scan(arg)
            return 1

        def update(self, ctx):
            # 메뉴 항목을 항상 활성화된 상태로 유지
            return idaapi.AST_ENABLE_ALWAYS
except AttributeError:
    pass

# IDA 플러그인을 정의하는 클래스
class binwalk_t(idaapi.plugin_t):
    flags = 0  # 플러그인 플래그
    comment = "Scan the current IDB for file signatures"  # 플러그인 설명
    help = ""  # 도움말 텍스트 (없음)
    wanted_name = "Binwalk IDA Plugin"  # 플러그인 이름
    wanted_hotkey = ""  # 핫키 (없음)

    def init(self):
        # IDA SDK 버전이 695 이하인 경우 메뉴 항목을 추가
        if idaapi.IDA_SDK_VERSION <= 695:
            self.menu_context_1 = idaapi.add_menu_item(
                "Search/", "binwalk opcodes", "", 0, self.opcode_scan, (None,))
            self.menu_context_2 = idaapi.add_menu_item(
                "Search/", "binwalk signatures", "", 0, self.signature_scan, (None,))

        # IDA SDK 버전이 700 이상인 경우, Actions API를 사용하여 메뉴 항목을 추가
        if idaapi.IDA_SDK_VERSION >= 700:
            # opcode 스캔 메뉴 항목을 추가
            action_desc = idaapi.action_desc_t(
                'my:opaction',  # 액션 이름 (고유 ID)
                'Binwalk opcodes',  # 액션에 대한 텍스트
                OpHandler(),  # 액션 핸들러
                '',  # 선택적 단축키
                'Binwalk opcodes',  # 선택적 툴팁
                )

            # 액션 등록
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(
                'Search/',
                'my:opaction',
                idaapi.SETMENU_APP)

            # signature 스캔 메뉴 항목을 추가
            action_desc = idaapi.action_desc_t(
                'my:sigaction',
                'Binwalk signatures',
                SigHandler(),
                '',
                'Binwalk signatures',
                )

            # 액션 등록
            idaapi.register_action(action_desc)
            idaapi.attach_action_to_menu(
                'Search/',
                'my:sigaction',
                idaapi.SETMENU_APP)

        else:
            pass

        return idaapi.PLUGIN_KEEP  # 플러그인을 유지

    def term(self):
        # IDA SDK 버전이 695 이하인 경우 메뉴 항목을 삭제
        if idaapi.IDA_SDK_VERSION <= 695:
            idaapi.del_menu_item(self.menu_context_1)
            idaapi.del_menu_item(self.menu_context_2)
        # IDA SDK 버전이 700 이상인 경우 액션을 메뉴에서 분리
        if idaapi.IDA_SDK_VERSION >= 700:
            idaapi.detach_action_from_menu(
                'Search/',
                'my:opaction')
            idaapi.detach_action_from_menu(
                'Search/',
                'my:sigaction')
        else:
            pass

        return None

    def run(self, arg):
        return None

    # IDB 파일에서 서명을 스캔하는 메서드
    def signature_scan(self, arg):
        binwalk.scan(idc.GetIdbPath(), signature=True)

    # IDB 파일에서 명령어(opcode)를 스캔하는 메서드
    def opcode_scan(self, arg):
        binwalk.scan(idc.GetIdbPath(), opcode=True)

# IDA 플러그인의 진입점을 정의
def PLUGIN_ENTRY():
    return binwalk_t()


repository: maskelihileci/Retrospective_Analysis
file: Retrospective_Analysis.py
description: Retrospective Analysis
wanted_name: Retrospective Analysis
comment: Retrospective Analysis Plugin
created_at: 2024-11-16 08:11:53+00:00
pushed_at: 2025-02-19 22:11:51+00:00
url:
https://github.com/maskelihileci/Retrospective_Analysis/blob/ce4c001b3743c2bb4a5a9b3ed8527874d2d20942/Retrospective_Analysis.py

content:
import ida_hexrays
import ida_kernwin
import ida_funcs
import idautils
import idaapi
import idc
import ida_typeinf
import ida_bytes
import os
import ida_diskio
import json
import ida_xref
import re


# Debug Logger sınıfı
class DebugLogger:
  _instance = None
  
  def __init__(self):
      self.enabled = False
      
  @classmethod
  def get_instance(cls):
      if cls._instance is None:
          cls._instance = cls()
      return cls._instance
      
  def set_enabled(self, enabled):
      self.enabled = enabled
      
  def debug(self, message):
      if self.enabled:
          print(f"[DEBUG] {message}")

# Progress dialog context manager
# Progress dialog güncelleme
class ProgressDialog:
  def __init__(self, message="Please wait..."):
      self.message = message
      
  def __enter__(self):
      ida_kernwin.show_wait_box(self.message)
      return self
      
  def __exit__(self, exc_type, exc_val, exc_tb):
      ida_kernwin.hide_wait_box()
  
  def replace_message(self, new_message):
      ida_kernwin.replace_wait_box(new_message)
  
  def check_cancelled(self):
      return ida_kernwin.user_cancelled()

class ConfigDialog(ida_kernwin.Form):
  def __init__(self):
      try:
          self.config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
          self.config = self.load_config()
          
          Form = ida_kernwin.Form
          
          Form.__init__(
              self,
              r"""Retrospective Analysis

              Analysis Settings:
              <##Analysis depth (1-10):{max_layers}>
              
              Analysis Options:
              <##Search Engine:{cGroup0}>
              <##Enable parameter analysis:{param_analysis}>{cGroup1}>
              <##Enable function type analysis:{func_type_analysis}>{cGroup2}>
              <##Enable unused parameter analysis in call references:{unused_param_analysis}>{cGroup3}>
              <##Enable virtual call analysis:{virtual_call_analysis}>{cGroup6}>
              
              Debug Options:
              <##Show debug messages:{show_debug}>{cGroup4}>
              
              View Options:
              <##Auto refresh decompiler views:{auto_refresh_views}>{cGroup5}>
                """, {
                    'max_layers': Form.NumericInput(tp=Form.FT_DEC, value=self.config["max_layers"], swidth=5),
                    'cGroup0': Form.DropdownListControl(items=["IDA-API","Hex-Rays"],),
                    'cGroup1': Form.ChkGroupControl(("param_analysis",), value=1 if self.config["param_analysis"] else 0),
                    'cGroup2': Form.ChkGroupControl(("func_type_analysis",), value=1 if self.config["func_type_analysis"] else 0),
                    'cGroup3': Form.ChkGroupControl(("unused_param_analysis",), value=1 if self.config["unused_param_analysis"] else 0),
                    'cGroup6': Form.ChkGroupControl(("virtual_call_analysis",), value=1 if self.config["virtual_call_analysis"] else 0),
                    'cGroup4': Form.ChkGroupControl(("show_debug",), value=1 if self.config["show_debug"] else 0),
                    'cGroup5': Form.ChkGroupControl(("auto_refresh_views",), value=1 if self.config["auto_refresh_views"] else 0)
                })          
          Form.Compile(self)
          # Dropdown'ın başlangıç değerini ayarla
          self.cGroup0.items = ["IDA-API", "Hex-Rays"]
          # Dropdown'ın başlangıç değerini ayarla
          if self.config["search_engine"] == "IDA-API":
                self.cGroup0.value = 0
          elif self.config["search_engine"] == "Hex-Rays":
                self.cGroup0.value = 1
          
      except Exception as e:
          print(f"Error initializing ConfigDialog: {str(e)}")
          raise

  def OnFormChange(self, fid):
        if fid == -1:  # form initialized
            return 1
        
        if fid == self.max_layers.id:
            # Değeri 1-10 arasında tut
            if self.max_layers.value < 1:
                self.max_layers.value = 1
            elif self.max_layers.value > 10:
                self.max_layers.value = 10
        
        return 1

  def load_config(self):
      default_config = {
          "max_layers": 4,
          "auto_refresh_views": True,
          "show_debug": False,
          "param_analysis": True,
          "func_type_analysis": True,
          "unused_param_analysis": True,
          "virtual_call_analysis": True,
          "search_engine" : "IDA-API"
      }
      
      try:
          if os.path.exists(self.config_path):
              with open(self.config_path, 'r') as f:
                  config = json.load(f)
                  # Validate max_layers
                  if "max_layers" in config:
                      config["max_layers"] = max(1, min(10, int(config["max_layers"])))

                  if "search_engine" not in config:
                      config["search_engine"] = "IDA-API"
                  return config
      except Exception as e:
          print(f"Error loading config: {e}")
      
      return default_config
  #
  def save_config(self):
      logger = DebugLogger.get_instance()
      try:
          config = {
              "max_layers": self.max_layers.value,
              "auto_refresh_views": bool(self.cGroup5.value),
              "show_debug": bool(self.cGroup4.value),
              "param_analysis": bool(self.cGroup1.value),
              "func_type_analysis": bool(self.cGroup2.value),
              "unused_param_analysis": bool(self.cGroup3.value),
              "virtual_call_analysis": bool(self.cGroup6.value),
              "search_engine": self.cGroup0.items[self.cGroup0.value]
          }
          
          with open(self.config_path, 'w') as f:
              json.dump(config, f, indent=4)
              logger.debug(f"\nConfiguration saved successfully to: {self.config_path}")
              
      except Exception as e:
          logger.debug(f"Error saving config: {str(e)}")

  def OnFormChange(self, fid):
        return 1

# Add ConfigActionHandler class
class ConfigActionHandler(idaapi.action_handler_t):
  def activate(self, ctx):
      try:
          dialog = ConfigDialog()
          result = dialog.Execute()
          if result == 1:
              dialog.save_config()
          dialog.Free()
          return 1
      except Exception as e:
          print(f"Error in config dialog: {e}")
          return 0

  def update(self, ctx):
      return idaapi.AST_ENABLE_ALWAYS
    
def show_config_dialog():
  try:
      dialog = None
      dialog = ConfigDialog()
      if dialog.Execute() == 1:
          dialog.save_config()
  except Exception as e:
      print(f"Error showing config dialog: {str(e)}")
  finally:
      if dialog:
          try:
              dialog.Free()
          except:
              pass


def first_config(self):
    try:
        self.config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
        
        # Define default config
        self.default_config = {
            "max_layers": 4,
            "auto_refresh_views": True,
            "show_debug": False,
            "param_analysis": True,
            "func_type_analysis": True,
            "unused_param_analysis": True,
            "virtual_call_analysis": True,
            "search_engine" : "IDA-API"
        }
        
        # Check if config file exists
        if os.path.exists(self.config_path):
            try:
                with open(self.config_path, 'r') as f:
                    current_config = json.load(f)
                
                # Check for missing values and add them
                updated = False
                for key, value in self.default_config.items():
                    if key not in current_config:
                        current_config[key] = value
                        updated = True
                        print(f"Missing config value added: {key} = {value}")
                
                # If changes were made, update the file
                if updated:
                    with open(self.config_path, 'w') as f:
                        json.dump(current_config, f, indent=4)
                    print("Config file updated")
                        
            except Exception as e:
                print(f"Error reading/writing config file: {str(e)}")
        else:
            # Create config file if it doesn't exist
            try:
                with open(self.config_path, 'w') as f:
                    json.dump(self.default_config, f, indent=4)
                print(f"Default config file created: {self.config_path}")
            except Exception as e:
                print(f"Error creating default config file: {str(e)}")

    except Exception as e:
        print(f"Error in first_config: {str(e)}")



class CallingConventionFixer:
    def __init__(self):
        self.processed_funcs = set()

    @staticmethod
    def is_64bit():
        seg = idaapi.getseg(idaapi.inf_get_start_ea())
        is_64bit = seg and seg.bitness == 2
        return is_64bit

    def print_function_info(self, func_ea):
        """Print all available information about a function"""
        logger = DebugLogger.get_instance()
        logger.debug(f"\n=== Function Info for {hex(func_ea)} ===")

        # Basic function info
        logger.debug(f"Name: {idc.get_func_name(func_ea)}")

        # Get tinfo
        tinfo = ida_typeinf.tinfo_t()
        if ida_typeinf.guess_tinfo(tinfo, func_ea):
            logger.debug(f"Type info: {tinfo}")

            # Get function details
            func_details = ida_typeinf.func_type_data_t()
            if tinfo.get_func_details(func_details):
                logger.debug(f"Calling convention: {func_details.cc}")
                logger.debug("Arguments:")
                for i, arg in enumerate(func_details):
                    logger.debug(f"  Arg {i}: {arg.type} {arg.name}")
                    if hasattr(arg, 'argloc'):
                        logger.debug(f"    Location: {arg.argloc}")

        # Try decompilation
        try:
            ida_hexrays.mark_cfunc_dirty(func_ea,False)
            cfunc = ida_hexrays.decompile(func_ea)
            if cfunc:
                logger.debug("\nDecompiled function type:")
                logger.debug(cfunc.type.dstr())
        except:
            logger.debug("Failed to decompile function")

        logger.debug("===========================\n")


    #
    def _getmt_cc_name(self,new_func_type_str):
        # Calling Convention isimlerini döndür
        logger = DebugLogger.get_instance()
        original_cc = ida_typeinf.CM_CC_UNKNOWN
        if "__thiscall" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_THISCALL
            logger.debug("[_getmt_cc_name] Calling convention: __thiscall")
        if "__fastcall" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_FASTCALL
            logger.debug("[_getmt_cc_name] Calling convention: __fastcall")
        if "__cdecl" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_CDECL
            logger.debug("[_getmt_cc_name] Calling convention: __cdecl")
        if "__stdcall" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_STDCALL
            logger.debug("[_getmt_cc_name] Calling convention: __stdcall")
        if "__usercall" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_SPECIAL
        if "__userpurge" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_SPECIAL
        if "__pascal" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_PASCAL
        if "__swiftcall" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_SWIFT
        if "__golang" in new_func_type_str:
            original_cc = ida_typeinf.CM_CC_GOLANG
        return original_cc
    
    def _getmt_rettype_name(self,new_func_type_str):
        logger = DebugLogger.get_instance()
        rettype = ida_typeinf.tinfo_t()
        return_type_str = new_func_type_str.split('(')[0].strip()
        return_type_str = re.sub(r'\bconst\b', '', return_type_str).strip() #Remove const keyword
        if return_type_str == "void":
            rettype.create_simple_type(ida_typeinf.BTF_VOID)
            logger.debug("[_getmt_rettype_name] Return type: void")
        elif return_type_str == "int" or return_type_str == "signed int":
            rettype.create_simple_type(ida_typeinf.BTF_INT)
            logger.debug("[_getmt_rettype_name] Return type: int")
        elif return_type_str == "char*":
            rettype.create_ptr(ida_typeinf.tinfo_t(ida_typeinf.BTF_CHAR))
            logger.debug("[_getmt_rettype_name] Return type: char*")
        elif return_type_str == "unsigned int" or return_type_str == "uint":
            rettype.create_simple_type(ida_typeinf.BTF_UINT)
            logger.debug("[_getmt_rettype_name] Return type: unsigned int")
        elif return_type_str == "long long" or return_type_str == "signed long long":
                rettype.create_simple_type(ida_typeinf.BTF_INT64)
                logger.debug("[_getmt_rettype_name] Return type: long long")
        elif return_type_str == "unsigned long long":
                rettype.create_simple_type(ida_typeinf.BTF_UINT64)
                logger.debug("[_getmt_rettype_name] Return type: unsigned long long")
        elif return_type_str == "float":
                rettype.create_simple_type(ida_typeinf.BTF_FLOAT)
                logger.debug("[_getmt_rettype_name] Return type: float")
        elif return_type_str == "double":
                rettype.create_simple_type(ida_typeinf.BTF_DOUBLE)
                logger.debug("[_getmt_rettype_name] Return type: double")
        elif return_type_str == "boolean":
                rettype.create_simple_type(ida_typeinf.BTF_BOOL)
                logger.debug("[_getmt_rettype_name] Return type: boolean")
        elif return_type_str.endswith('*'):
            rettype.create_ptr(ida_typeinf.tinfo_t(ida_typeinf.BTF_VOID))
            logger.debug(f"[_getmt_rettype_name] Return type: pointer (default void *)")
        else:
            rettype.create_simple_type(ida_typeinf.BTF_INT)  # Default return type
            logger.debug(f"[_getmt_rettype_name] Return type: Unknown, default int")
        return rettype
    
    def _analyze_virtual_calls_and_update_signature(self,func_ea):
        """Analyze virtual calls, find undefined variables, and update their types."""
        logger = DebugLogger.get_instance()
        logger.debug(f"[_analyze_virtual_calls_and_update_signature] Analyzing function at {hex(func_ea)}")
        config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)
        except:
            return False

        if not config.get("virtual_call_analysis", True):
            return False

        try:
            ida_hexrays.mark_cfunc_dirty(func_ea, False)
            cfunc = ida_hexrays.decompile(func_ea)
            if not cfunc:
                logger.debug(f"[_analyze_virtual_calls_and_update_signature] Could not decompile function at {hex(func_ea)}")
                return False
            logger.debug(f"[_analyze_virtual_calls_and_update_signature] Function at {hex(func_ea)} decompiled successfully.")

            class UndefinedVarCollector(ida_hexrays.ctree_visitor_t):
                def __init__(self, cfunc):
                    ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                    self.cfunc = cfunc
                    self.undefined_vars = set()
                    self.undefined_vars_propagated = set()
                    self.var_assignments = {}
                    self.virtual_calls = {}
                    self.undefined_vars_in_helpers = {}
                    self.helper_var_types = {} 
                    self.size_functions = {
                        "LOBYTE": ida_typeinf.BTF_UINT8,
                        "HIBYTE": ida_typeinf.BTF_UINT8,
                        "LOWORD": ida_typeinf.BTF_UINT16,
                        "HIWORD": ida_typeinf.BTF_UINT16,
                        "LODWORD": ida_typeinf.BTF_UINT32,
                        "HIDWORD": ida_typeinf.BTF_UINT32,
                        "BYTE": ida_typeinf.BTF_UINT8,
                        "WORD": ida_typeinf.BTF_UINT16,
                        "DWORD": ida_typeinf.BTF_UINT32,
                        "QWORD": ida_typeinf.BTF_UINT64
                    }
                    logger.debug(f"[UndefinedVarCollector] Initialized collector for function at {hex(cfunc.entry_ea)}")

                def _is_undefined(self, expr):
                    """Check if an expression is an undefined variable."""

                    if expr.op != ida_hexrays.cot_var:
                        return False

                    lvar = self.cfunc.get_lvars()[expr.v.idx]
                    if not lvar:
                        return False

                    if hasattr(lvar, 'has_user_info') and lvar.has_user_info and hasattr(lvar, 'user_info') and lvar.user_info is None:
                            return True

                    if expr.exflags & ida_hexrays.EXFL_UNDEF:
                            return True

                    if expr.v.idx in self.undefined_vars_propagated:
                            return True
                    return False


                def visit_expr(self, expr):
                    if expr.op == ida_hexrays.cot_call and expr.x.op == ida_hexrays.cot_helper:
                        helper_name = expr.x.helper
                        if helper_name in self.size_functions and expr.a and expr.a[0].op == ida_hexrays.cot_var:
                            var_idx = expr.a[0].v.idx
                            self.helper_var_types[var_idx] = self.size_functions[helper_name]

                    if expr.op == ida_hexrays.cot_var and hasattr(expr,'v') and hasattr(expr.v,'idx'):
                        lvar_index = expr.v.idx
                        if self._is_undefined(expr):
                            self.undefined_vars.add(lvar_index)
                            logger.debug(f"[UndefinedVarCollector] Added undefined variable index: {lvar_index}")
                    if expr.op == ida_hexrays.cot_asg and hasattr(expr, 'x') and hasattr(expr.x, 'v') and hasattr(expr.x.v,'idx'):
                        lvar_index = expr.x.v.idx
                        if self._is_undefined(expr.y):
                            if lvar_index not in self.undefined_vars_propagated:
                                self.undefined_vars_propagated.add(lvar_index)
                                self.undefined_vars.add(lvar_index)
                                logger.debug(f"[UndefinedVarCollector] Propagated Undefined var via assignment: {lvar_index}")
                        elif hasattr(expr.y, 'op') and expr.y.op == ida_hexrays.cot_var and hasattr(expr.y,'v') and hasattr(expr.y.v,'idx') and expr.y.v.idx in self.undefined_vars_propagated:
                            if lvar_index not in self.undefined_vars_propagated:
                                self.undefined_vars_propagated.add(lvar_index)
                                self.undefined_vars.add(lvar_index)
                                logger.debug(f"[UndefinedVarCollector] Propagated Undefined var via assignment from propagated: {lvar_index}")
                    return 0


                def traverse_tree_and_collect_vars(self, expr):
                    """
                    Traverse the given tree and collect variable names and
                    corresponding cexpr_t objects.

                    Args:
                        expr: The root node of the tree as a cexpr_t object.
                        cfunc: The function cfunc_t object.

                    Returns:
                        A list of tuples containing the variable names and
                        cexpr_t objects found in the tree. Each element is a
                        tuple of (var_name, cexpr_t).
                    """
                    var_info = []  # List to store (var_name, cexpr_t) tuples
                    added_vars = set() # Set to store added variable names

                    def _traverse(expr):
                        if expr is None:
                            return

                        if expr.op == ida_hexrays.cot_call:
                            # Traverse function call arguments
                            if expr.a:
                                for arg in expr.a:
                                    _traverse(arg)
                            # Skip function name (x)
                            return

                        if expr.op == ida_hexrays.cot_var:
                            lvar = self.cfunc.get_lvars()[expr.v.idx]
                            if lvar and lvar.name not in added_vars:
                                var_info.append((lvar.name, expr))
                                added_vars.add(lvar.name)

                        # Traverse the tree
                        if expr.a:
                            for arg in expr.a:
                                _traverse(arg)

                        if expr.x:
                            _traverse(expr.x)
                        if expr.y:
                            _traverse(expr.y)
                        if expr.z:
                            _traverse(expr.z)

                    _traverse(expr)
                    return var_info
                

            collector = UndefinedVarCollector(cfunc)
            collector.apply_to(cfunc.body, None)
            logger.debug(f"Undefined vars: {collector.undefined_vars}, Helper types: {collector.helper_var_types}")

            class UndefinedCallVisitor(ida_hexrays.ctree_visitor_t):
                def __init__(self, cfunc, collector):
                    ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                    self.cfunc = cfunc
                    self.collector = collector
                    self.virtual_calls = {}

                def visit_expr(self, expr):
                    if expr.op == ida_hexrays.cot_call:
                        indices = []
                        if expr.a:
                            for i, arg in enumerate(expr.a):
                                var_info = self.collector.traverse_tree_and_collect_vars(arg)
                                for var_name, var_expr in var_info:
                                    var_idx = var_expr.v.idx
                                    if var_idx in self.collector.undefined_vars or var_idx in self.collector.helper_var_types:
                                        if i not in indices:
                                            indices.append(i)
                        if indices:
                            self.virtual_calls[expr.ea] = indices
                    return 0

            callVisitor = UndefinedCallVisitor(cfunc, collector)
            callVisitor.apply_to(cfunc.body, None)
            logger.debug(f"Detected virtual calls: {callVisitor.virtual_calls}")

            # Update virtual call types
            for call_ea, undefined_args in callVisitor.virtual_calls.items():
                # Get cexpr from the address
                class ExprFinder(ida_hexrays.ctree_visitor_t):
                    def __init__(self, target_ea):
                        ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                        self.target_ea = target_ea
                        self.found_expr = None
                        logger.debug(f"[ExprFinder] Initialized to find expression at {hex(target_ea)}")

                    def visit_expr(self, expr):
                        logger.debug(f"[ExprFinder.visit_expr] Visiting expression: {expr} at {hex(expr.ea)}")
                        logger.debug(f"[ExprFinder.visit_expr] Call expression: {expr} at {hex(call_ea)}")
                        if expr.ea == self.target_ea:
                            if expr.a is None:
                                return 0
                            self.found_expr = expr
                            logger.debug(f"[ExprFinder.visit_expr] Found target expression: {expr}")
                            return 1
                        return 0
                expr_finder = ExprFinder(call_ea)
                expr_finder.apply_to(cfunc.body, None)
                if expr_finder.found_expr:
                    func_ptr_ea = expr_finder.found_expr.x.ea if hasattr(expr_finder.found_expr, 'x') else idaapi.BADADDR
                    if func_ptr_ea == idaapi.BADADDR:
                        func_ptr_ea = expr_finder.found_expr.ea if hasattr(expr_finder.found_expr, 'ea') else idaapi.BADADDR
                    logger.debug(f"[_analyze_virtual_calls_and_update_signature] Found call expression at {hex(call_ea)} with function pointer at {hex(func_ptr_ea)}")
                    if func_ptr_ea and func_ptr_ea != idaapi.BADADDR:
                        self._update_virtual_call_type(expr_finder.found_expr, func_ptr_ea, cfunc, undefined_args, expr_finder,collector)
                    else:
                        logger.debug(f"[_analyze_virtual_calls_and_update_signature] Skip call type update {hex(call_ea)} because function address could not be found.")

            return True
        except Exception as e:
            logger.debug(f"[_analyze_virtual_calls_and_update_signature] Error analyzing virtual calls for {hex(func_ea)}: {str(e)}")
            return False
        
    def _update_virtual_call_type(self,call_expr, func_ptr_ea, cfunc, undefined_args, expr_finder,collector):
        logger = DebugLogger.get_instance()
        logger.debug(f"[_update_virtual_call_type] Updating virtual call type at {hex(call_expr.ea)}, func_ptr_ea: {hex(func_ptr_ea)}, undefined_args: {undefined_args}")
        try:
            if not call_expr or not func_ptr_ea or func_ptr_ea == idaapi.BADADDR:
                logger.debug("[_update_virtual_call_type] Invalid call expression or function pointer")
                return False

            current_ea = call_expr.ea if hasattr(call_expr, 'ea') else idaapi.BADADDR
            if current_ea == idaapi.BADADDR:
                logger.debug("[_update_virtual_call_type] Invalid EA value")
                return False

            # Log original information
            logger.debug("\n=== Original Call Information ===")
            logger.debug(f"Call Address: {hex(current_ea)}")
            logger.debug(f"Function Pointer: {hex(func_ptr_ea)}")
            if hasattr(call_expr, 'x') and hasattr(call_expr.x, 'type'):
                logger.debug(f"Return Type: {call_expr.x.type.get_rettype()}")
                logger.debug(f"Original Function Type: {call_expr.x.type}")
                original_func_type = call_expr.x.type
                logger.debug(f"Original_func_type : {original_func_type}")
                logger.debug(f"Original_func_type type : {type(original_func_type)}")
            else:
                logger.debug(f"Return Type: N/A")
                logger.debug("[_update_virtual_call_type] Original Function Type not found")
                original_func_type = None

            if expr_finder and expr_finder.found_expr:
                try:
                    # Get the virtual call expression
                    vcall_expr = expr_finder.found_expr
                    logger.debug(f"[_update_virtual_call_type] Found virtual call expression: {vcall_expr}")

                    # Create new type info for the virtual function call
                    new_func_type_str = ""

                    if original_func_type:
                        new_func_type_str = str(original_func_type)
                        logger.debug(f"[_update_virtual_call_type] Original function type string: {new_func_type_str}")

                    new_tinfo = ida_typeinf.tinfo_t()
                    ftd = ida_typeinf.func_type_data_t()
                    if hasattr(call_expr.x, 'type') and hasattr(call_expr.x.type,'get_rettype'):
                        ftd.rettype = call_expr.x.type.get_rettype()
                    else:
                        ftd.rettype = self._getmt_rettype_name(new_func_type_str)
                    ftd.cc = self._getmt_cc_name(new_func_type_str)  # Set the calling convention

                    # Corrected argument type handling
                    for i in range(len(call_expr.a) if call_expr.a else 0):
                        if i in undefined_args:  # Only process arguments that need to be corrected
                            arg = call_expr.a[i]
                            has_helper = False
                            
                            # Helper type check
                            if arg.op == ida_hexrays.cot_var:
                                var_idx = arg.v.idx
                                if var_idx in collector.helper_var_types:
                                    new_arg = ida_typeinf.funcarg_t()
                                    new_arg.type.create_simple_type(collector.helper_var_types[var_idx])
                                    ftd.push_back(new_arg)
                                    has_helper = True
                                    logger.debug(f"Argument {i} corrected with helper type")
                            
                            # Nested helper check
                            if not has_helper:
                                var_info = collector.traverse_tree_and_collect_vars(arg)
                                for var_name, var_expr in var_info:
                                    var_idx = var_expr.v.idx
                                    if var_idx in collector.helper_var_types:
                                        new_arg = ida_typeinf.funcarg_t()
                                        new_arg.type.create_simple_type(collector.helper_var_types[var_idx])
                                        ftd.push_back(new_arg)
                                        has_helper = True
                                        logger.debug(f"Argument {i} corrected with nested helper type")
                                        break
                            
                            if not has_helper:
                                logger.debug(f"Argument {i} is undefined and has no helper, SKIPPED")
                        else:
                            # Preserve original argument
                            original_arg = ida_typeinf.funcarg_t()
                            original_arg.type = call_expr.a[i].type
                            ftd.push_back(original_arg)
                            logger.debug(f"Argument {i} preserved as original")

                    if not new_tinfo.create_func(ftd):
                        logger.debug(f"[_update_virtual_call_type] Failed to create func type with func_type_data_t")
                        ida_typeinf.clear_tinfo_t(new_tinfo)
                        return False

                    new_ptr_tinfo = ida_typeinf.tinfo_t()
                    new_ptr_tinfo.create_ptr(new_tinfo)

                    logger.debug(f"\n=== New Type Information ===")
                    logger.debug(f"Function type: {new_tinfo}")
                    logger.debug(f"Pointer type: {new_ptr_tinfo}")

                    # Update the call expression type
                    if hasattr(vcall_expr, 'x') and vcall_expr.x:

                        # Update the function pointer type in expression
                        if idaapi.set_op_tinfo(vcall_expr.x.ea, 0, new_ptr_tinfo):
                            idaapi.set_op_tinfo(vcall_expr.ea, 0, new_tinfo)
                            pass
                        elif not idaapi.set_op_tinfo(vcall_expr.ea, 0, new_tinfo):
                            logger.debug(f"[_update_virtual_call_type] Failed to set new tinfo for call expression")
                            ida_typeinf.clear_tinfo_t(new_tinfo)
                            return False
                        logger.debug(f"[_update_virtual_call_type] Updated function pointer type: {new_ptr_tinfo}")

                    ida_hexrays.mark_cfunc_dirty(cfunc.entry_ea, False)
                    cfunc = ida_hexrays.decompile(cfunc.entry_ea)
                    if cfunc:
                        logger.debug("\n=== Call Update Status ===")
                        logger.debug(f"Successfully updated virtual call at {hex(current_ea)}")
                        logger.debug(f"New function type: {new_tinfo}")
                        ida_typeinf.clear_tinfo_t(new_tinfo)
                        return True
                    else:
                        logger.debug(" [_update_virtual_call_type] Failed to get function object after update.")
                        ida_typeinf.clear_tinfo_t(new_tinfo)
                        return False

                except Exception as e:
                    logger.debug(f"[_update_virtual_call_type] === Expression Update Error ===")
                    logger.debug(f"[_update_virtual_call_type] Error: {str(e)}")
                    logger.debug(f"[_update_virtual_call_type] Error type: {type(e)}")
                    raise
            else:
                logger.debug(" [_update_virtual_call_type] Could not find target expression")
                return False

        except Exception as e:
            logger.debug(f"[_update_virtual_call_type] === Error Information ===")
            logger.debug(f"[_update_virtual_call_type] Error in _update_virtual_call_type: {str(e)}")
            logger.debug(f"[_update_virtual_call_type] Error type: {type(e)}")
            return False
        
    def _is_virtual_call(self, expr):
            """Check if an expression is a virtual call."""
            if expr.op != ida_hexrays.cot_call:
                return False

            if hasattr(expr, 'x') and self._is_potential_vtable_access(expr.x):
                return True
            return False

    def _is_potential_vtable_access(self, expr):
            """Check various vtable access patterns"""
            logger = DebugLogger.get_instance()
            if not expr:
                return False

            try:
                # Pattern 1: Direct pointer dereference
                if expr.op == ida_hexrays.cot_ptr:
                    return True

                # Pattern 2: Member pointer access
                if expr.op == ida_hexrays.cot_memptr:
                    return True

                # Pattern 3: Addition operation (possible vtable offset)
                if expr.op == ida_hexrays.cot_add:
                    if hasattr(expr, 'x') and expr.x:
                        if expr.x.op == ida_hexrays.cot_ptr:
                            return True
                            #if self._check_expr_for_param(expr.x, "vtable base", True):
                            #   return True

                # Pattern 4: Cast operations
                if expr.op == ida_hexrays.cot_cast:
                    return self._is_potential_vtable_access(expr.x if hasattr(expr, 'x') else None)
                
            except Exception as ex:
                 logger.debug(f"Error in _is_potential_vtable_access: {str(ex)}")

            return False

    def _analyze_calls_and_update_signature(self, func_ea):
        """Analyze all calls to a function and determine if parameters are undefined"""
        logger = DebugLogger.get_instance()
        
        # Load config
        config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)
        except:
            return False

        if not config.get("unused_param_analysis", True):
            return False

        try:
            # Get all references to this function
            xrefs = []
            for xref in idautils.XrefsTo(func_ea, 0):
                if xref.type in [ida_xref.fl_CN, ida_xref.fl_CF]:
                    xrefs.append(xref.frm)

            if not xrefs:
                logger.debug(f"No calls found for function at {hex(func_ea)}")
                return False

            # Get function type information
            tinfo = ida_typeinf.tinfo_t()
            func_details = ida_typeinf.func_type_data_t()

            # Track parameter usage across all calls
            param_count = func_details.size()
            param_usage = {i: {'undefined_count': 0, 'total_calls': 0} for i in range(param_count)}
            
            if config.get("show_debug", False):
                logger.debug(f"\nAnalyzing calls for function at {hex(func_ea)}")
                logger.debug(f"Number of parameters: {param_count}")
                logger.debug(f"Number of calls: {len(xrefs)}")

            #
            def types_compatible(actual_type, expected_type):
                """Check if two types are compatible"""
                try:
                    # Basic type compatibility checks
                    if actual_type.is_ptr() and not expected_type.is_ptr():
                        return False
                    if actual_type.is_int() and expected_type.is_float():
                        return False
                    if actual_type.is_float() and expected_type.is_int():
                        return False
                        # Null kontrolleri eksik
                    if actual_type is None or expected_type is None:
                        return False                       
                    # Tüm tip kombinasyonları kontrol edilmemiş
                    if actual_type.is_array() and not expected_type.is_array():
                        return False
                    return True
                except:
                    return True
            def check_function_parameter_mismatch(expr, cfunc):
                """Check for potential parameter count mismatches in function calls"""
                try:
                    if expr.op == ida_hexrays.cot_call:
                        # Get function type information
                        func_type = expr.x.type
                        if func_type.is_func():
                            expected_args = func_type.get_nargs()
                            actual_args = expr.a.size()
                            
                            # Check for significant parameter count mismatch
                            if actual_args < expected_args:
                                return True
                            
                            # Check argument types if available
                            for i in range(min(actual_args, expected_args)):
                                arg = expr.a[i]
                                expected_type = func_type.get_nth_arg(i)
                                
                                # Check for potential type mismatches
                                if not types_compatible(arg.type, expected_type):
                                    return True
                except:
                    return False
                return False
            def check_undefined_patterns(expr, cfunc):
                """Check various patterns that indicate undefined values using Hex-Rays API."""
                try:
                    # Check if the expression is a variable
                    if expr.op == ida_hexrays.cot_var:
                        lvar = cfunc.get_lvars()[expr.v.idx]
                        if not lvar:
                            return False

                    # Check if the expression is a function call
                    elif expr.op == ida_hexrays.cot_call:
                        if check_function_parameter_mismatch(expr, cfunc):
                            return True

                    # Check if the expression is undefined using exflags
                    if expr.exflags & ida_hexrays.EXFL_UNDEF:
                        return True

                    # Check for suspicious constant values
                    elif expr.op == ida_hexrays.cot_num:
                        suspicious_values = {
                            0xBADF00D, 0xDEADBEEF, 0xCCCCCCCC,
                            0xFEEEFEEE, 0xCDCDCDCD, 0xABABABAB,
                            0xFFFFFFFE, 0xFFFFFFFF, 0x0,
                            0xDEADC0DE, 0xDEADBEEF
                        }
                        if expr.numval() in suspicious_values:
                            return True

                    # Check for null or invalid pointers
                    elif expr.op == ida_hexrays.cot_ptr:
                        if expr.x.op == ida_hexrays.cot_num:
                            val = expr.x.numval()
                            if val < 0x1000:  # Likely null or invalid pointer
                                return True

                    # Check for memory accesses
                    elif expr.op in [ida_hexrays.cot_memptr, ida_hexrays.cot_memref]:
                        if expr.x.op == ida_hexrays.cot_var:
                            # Check if base pointer is undefined
                            if check_undefined_patterns(expr.x, cfunc):
                                return True

                except Exception as e:
                    logger.debug(f"Error in check_undefined_patterns: {str(e)}")
                    return False

                return False

            # Analyze each call
            for call_ea in xrefs:
                try:
                    caller_func = ida_funcs.get_func(call_ea)
                    if not caller_func:
                        continue
                    ida_hexrays.mark_cfunc_dirty(caller_func.start_ea,False)
                    cfunc = ida_hexrays.decompile(caller_func.start_ea)
                    if not cfunc:
                        continue

                    class CallArgVisitor(ida_hexrays.ctree_visitor_t):
                        def __init__(self, target_ea, param_count):
                            super().__init__(ida_hexrays.CV_FAST)
                            self.target_ea = target_ea
                            self.param_count = param_count
                            self.call_found = False
                            self.arg_status = []

                        def visit_expr(self, expr):
                            if expr.op == ida_hexrays.cot_call and expr.ea == self.target_ea:
                                self.call_found = True
                                
                                # Check actual arguments passed
                                for i in range(min(len(expr.a), self.param_count)):
                                    is_undefined = check_undefined_patterns(expr.a[i], cfunc)
                                    self.arg_status.append(is_undefined)
                                
                                # If fewer args than params, mark remaining as undefined
                                while len(self.arg_status) < self.param_count:
                                    self.arg_status.append(True)
                                
                                return 1
                            return 0

                    visitor = CallArgVisitor(call_ea, param_count)
                    visitor.apply_to(cfunc.body, None)

                    if visitor.call_found:
                        for i, is_undefined in enumerate(visitor.arg_status):
                            if i in param_usage:
                                param_usage[i]['total_calls'] += 1
                                if is_undefined:
                                    param_usage[i]['undefined_count'] += 1

                        if config.get("show_debug", False):
                            logger.debug(f"\nCall at {hex(call_ea)}:")
                            for i, status in enumerate(visitor.arg_status):
                                logger.debug(f"Param {i}: {'Undefined' if status else 'Defined'}")

                except Exception as e:
                    logger.debug(f"Error analyzing call at {hex(call_ea)}: {str(e)}")
                    continue

            # Calculate undefined ratio and track actually used parameters
            undefined_ratios = {}
            actually_used_params = []
            for param_idx, stats in param_usage.items():
                if stats['total_calls'] > 0:
                    ratio = stats['undefined_count'] / stats['total_calls']
                    undefined_ratios[param_idx] = ratio
                    if ratio < 0.8:  # Parameter is defined in >20% of calls
                        actually_used_params.append(param_idx)

            if config.get("show_debug", False):
                logger.debug("\nParameter undefined ratios:")
                for param_idx, ratio in undefined_ratios.items():
                    logger.debug(f"Param {param_idx}: {ratio:.2%} undefined ({param_usage[param_idx]['undefined_count']}/{param_usage[param_idx]['total_calls']} calls)")

            # Update function type if needed
            original_param_count = func_details.size()
            new_param_count = len(actually_used_params)

            if new_param_count < original_param_count:
                try:
                    ida_hexrays.mark_cfunc_dirty(func_ea, False)
                    cfunc = ida_hexrays.decompile(func_ea)
                    if not cfunc:
                        logger.debug("Failed to decompile function")
                        return False
                        
                    original_tinfo = cfunc.type
                    original_details = ida_typeinf.func_type_data_t()
                    
                    if not original_tinfo.get_func_details(original_details):
                        logger.debug("Failed to get original function details")
                        return False
                        
                    # Store original calling convention
                    original_cc = original_details.cc
                    logger.debug(f"Original calling convention (before): {original_cc}")
                    
                    # Create new function type with only actually used parameters
                    new_func_details = ida_typeinf.func_type_data_t()
                    new_func_details.rettype = original_details.rettype
                    
                    # Thiscall kontrolü
                    if original_cc == ida_typeinf.CM_CC_THISCALL and new_param_count == 0:
                        logger.debug("Thiscall detected with no parameters, converting to fastcall")
                        new_func_details.cc = ida_typeinf.CM_CC_FASTCALL
                    else:
                        new_func_details.cc = original_cc
                    
                    for idx in actually_used_params:
                        new_func_details.push_back(original_details[idx])
                    
                    new_tinfo = ida_typeinf.tinfo_t()
                    if new_tinfo.create_func(new_func_details):
                        logger.debug(f"New type info: {new_tinfo}")
                        
                        # Apply the new type
                        if ida_typeinf.apply_tinfo(func_ea, new_tinfo, ida_typeinf.TINFO_DEFINITE):
                            # Verify the final type
                            cfunc = ida_hexrays.decompile(func_ea)
                            final_tinfo = cfunc.type
                            
                            final_details = ida_typeinf.func_type_data_t()
                            final_tinfo.get_func_details(final_details)
                            
                            logger.debug(f"Final calling convention: {final_details.cc}")
                            
                            logger.debug(f"\nSuccessfully updated function type at {hex(func_ea)}")
                            logger.debug(f"Original parameter count: {original_param_count}")
                            logger.debug(f"New parameter count: {new_param_count}")
                            logger.debug(f"Actually used parameters: {actually_used_params}")
                            return True
                            
                except Exception as e:
                    logger.debug(f"Error updating function type: {str(e)}")
                    return False

                return False

        except Exception as e:
            logger.debug(f"Error analyzing calls for function at {hex(func_ea)}: {str(e)}")
            return False

    def _determine_new_calling_convention(self, original_cc, really_used_params, total_params):
        logger = DebugLogger.get_instance()

        # Calling convention specific register usage maps
        CC_REGISTER_MAPS = {
            ida_typeinf.CM_CC_FASTCALL: {
                "x64": ["rcx", "rdx", "r8", "r9"],
                "x86": ["ecx", "edx"]
            },
            ida_typeinf.CM_CC_THISCALL: {
                "x86": ["ecx"],
                "x64": ["rcx"]
            }
        }

        is_64bit = self.is_64bit()
        arch = 'x64' if is_64bit else 'x86'

        # Thiscall handling
        if original_cc == ida_typeinf.CM_CC_THISCALL:
            if 0 not in really_used_params:
                logger.debug("Thiscall with unused 'this' parameter - converting")
                if is_64bit:
                    return ida_typeinf.CM_CC_FASTCALL
                else:
                    return ida_typeinf.CM_CC_STDCALL
            return original_cc

        # Fastcall handling
        if original_cc == ida_typeinf.CM_CC_FASTCALL:
            register_count = len(CC_REGISTER_MAPS[ida_typeinf.CM_CC_FASTCALL][arch])
            unused_registers = [i for i in range(register_count) if i not in really_used_params]
            later_params_used = any(i in really_used_params for i in range(register_count, total_params))

            if unused_registers and later_params_used:
                if not is_64bit:  # x86 only
                    return ida_typeinf.CM_CC_CDECL

        return original_cc


    def _analyze_and_remove_unused_parameters(self, func_ea):
        """Analyze and remove unused parameters for all calling conventions"""
        logger = DebugLogger.get_instance()

        # Load config
        config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)
        except:
            return False

        if not config.get("param_analysis", True):
            return False

        try:
            # Decompile the function
            ida_hexrays.mark_cfunc_dirty(func_ea, False)
            cfunc = ida_hexrays.decompile(func_ea)
            if not cfunc:
                return False

            # Get function type information
            tinfo = cfunc.type
            func_details = ida_typeinf.func_type_data_t()
            if not tinfo.get_func_details(func_details):
                return False

            class ParamVisitor(ida_hexrays.ctree_visitor_t):
                def __init__(self, cfunc):
                    ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                    self.cfunc = cfunc
                    self.used_params = set()
                    self.param_references = {}  # Track parameter references

                def _get_param_index(self, lvar):
                    if not lvar.is_arg_var:
                        return None

                    # Find position in arguments list
                    for i, arg in enumerate(self.cfunc.arguments):
                        if arg == lvar:
                            return i
                    return None

                def visit_expr(self, expr):
                    if expr.op == ida_hexrays.cot_var:
                        lvar = self.cfunc.get_lvars()[expr.v.idx]
                        param_idx = self._get_param_index(lvar)
                        if param_idx is not None:
                            self.used_params.add(param_idx)
                            if param_idx not in self.param_references:
                                self.param_references[param_idx] = []
                            self.param_references[param_idx].append(expr.ea)
                    return 0

            # First pass - collect parameter references
            visitor = ParamVisitor(cfunc)
            visitor.apply_to(cfunc.body, None)
            initial_used_params = visitor.used_params

            # Second pass - check real usage
            really_used_params = set()
            for param_idx in initial_used_params:
                if self._is_parameter_really_used(cfunc, param_idx, visitor.param_references.get(param_idx, [])):
                    really_used_params.add(param_idx)

            # Debug info
            if config.get("show_param_analysis", False):
                logger.debug(f"\nParameter Usage Analysis for function at {hex(func_ea)}:")
                logger.debug(f"Total parameters: {len(func_details)}")
                for i in range(len(func_details)):
                    param_type = str(func_details[i].type)
                    param_name = func_details[i].name
                    is_referenced = i in initial_used_params
                    is_really_used = i in really_used_params
                    refs = visitor.param_references.get(i, [])
                    logger.debug(f"Parameter {i}: {param_type} {param_name}")
                    logger.debug(f"  Referenced: {is_referenced}")
                    logger.debug(f"  Actually Used: {is_really_used}")
                    logger.debug(f"  References: {[hex(ref) for ref in refs]}")

            # Determine new calling convention
            new_cc = self._determine_new_calling_convention(
                func_details.cc,
                really_used_params,
                len(func_details)
            )

            # Create new function details
            new_func_details = ida_typeinf.func_type_data_t()
            new_func_details.rettype = func_details.rettype
            new_func_details.cc = new_cc

            # Log calling convention change
            if new_cc != func_details.cc:
                logger.debug(f"Calling convention changed from {func_details.cc} to {new_cc}")

            # Handle parameters based on calling convention
            if new_cc == ida_typeinf.CM_CC_THISCALL:
                # Always include 'this' parameter for thiscall
                new_func_details.push_back(func_details[0])
                # Add other used parameters
                for i in range(1, len(func_details)):
                    if i in really_used_params:
                        new_func_details.push_back(func_details[i])
            else:
                # For other calling conventions, add only used parameters
                stack_args = [arg for arg in cfunc.arguments if arg.is_stk_var()]
                
                if stack_args:
                    first_param_idx = cfunc.arguments.index(stack_args[0])

                    if first_param_idx not in really_used_params and any(i in really_used_params for i in range(first_param_idx + 1, len(func_details))):
                        logger.debug(f"First stack parameter (index {first_param_idx}) is unused but later stack parameters are used. Keeping it.")
                        new_func_details.push_back(func_details[first_param_idx])

                for i in range(len(func_details)):
                    if i in really_used_params:
                        new_func_details.push_back(func_details[i])

            # Create and apply new type
            new_tinfo = ida_typeinf.tinfo_t()
            new_tinfo.create_func(new_func_details)

            if ida_typeinf.apply_tinfo(func_ea, new_tinfo, ida_typeinf.TINFO_DEFINITE):
                logger.debug(f"\nSuccessfully updated function type at {hex(func_ea)}")
                logger.debug(f"Original parameter count: {len(func_details)}")
                logger.debug(f"New parameter count: {len(new_func_details)}")
                logger.debug(f"Actually used parameters: {sorted(list(really_used_params))}")
                return True

            return False

        except Exception as e:
            logger.debug(f"Error analyzing parameters at {hex(func_ea)}: {str(e)}")
            return False

    def _is_parameter_really_used(self, cfunc, param_idx, param_refs):
        """Check if a parameter is really used"""
        if not param_refs:
            return False

        class UsageChecker(ida_hexrays.ctree_visitor_t):
            def __init__(self, cfunc):
                ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                self.is_used = False
                self.cfunc = cfunc

            def visit_expr(self, expr):
                if expr.ea in param_refs:
                    # If this is a variable
                    if expr.op == ida_hexrays.cot_var:
                        # Check parent expressions
                        parent_expr = self.find_parent_expr(expr)
                        if parent_expr:
                            if parent_expr.op in [
                                # Assignment operations
                                ida_hexrays.cot_asg,      # Normal assignment
                                ida_hexrays.cot_asgadd,   # += assignment
                                ida_hexrays.cot_asgsub,   # -= assignment
                                ida_hexrays.cot_asgmul,   # *= assignment
                                ida_hexrays.cot_asgsdiv,   # /= s.assignment
                                ida_hexrays.cot_asgudiv,   # /= u.assignment
                                ida_hexrays.cot_asgsmod,   # %= s.assignment
                                ida_hexrays.cot_asgumod,   # %= u.assignment
                                ida_hexrays.cot_asgband,   # &= assignment
                                ida_hexrays.cot_asgbor,    # |= assignment
                                ida_hexrays.cot_asgxor,   # ^= assignment

                                # Function calls
                                ida_hexrays.cot_call,     # Function call

                                # Array and pointer operations
                                ida_hexrays.cot_idx,      # Array index
                                ida_hexrays.cot_ptr,      # Pointer dereferencing
                                ida_hexrays.cot_memref,   # Struct/Union member access
                                ida_hexrays.cot_memptr,   # Struct/Union pointer member access

                                # Arithmetic operations
                                ida_hexrays.cot_add,      # Addition
                                ida_hexrays.cot_sub,      # Subtraction
                                ida_hexrays.cot_mul,      # Multiplication
                                ida_hexrays.cot_sdiv,     # Signed division
                                ida_hexrays.cot_udiv,     # Unsigned division
                                ida_hexrays.cot_fdiv,     # Floating point division
                                ida_hexrays.cot_smod,     # Signed modulo
                                ida_hexrays.cot_umod,     # Unsigned modulo

                                # Comparison operations
                                ida_hexrays.cot_eq,       # Equality ==
                                ida_hexrays.cot_ne,       # Inequality !=
                                ida_hexrays.cot_slt,      # Signed less than <
                                ida_hexrays.cot_sle,      # Signed less than or equal <=
                                ida_hexrays.cot_sgt,      # Signed greater than >
                                ida_hexrays.cot_sge,      # Signed greater than or equal >=
                                ida_hexrays.cot_ult,      # Unsigned less than
                                ida_hexrays.cot_ule,      # Unsigned less than or equal
                                ida_hexrays.cot_ugt,      # Unsigned greater than
                                ida_hexrays.cot_uge,      # Unsigned greater than or equal

                                # Logical operations
                                ida_hexrays.cot_land,     # Logical AND &&
                                ida_hexrays.cot_lor,      # Logical OR ||
                                ida_hexrays.cot_band,     # Bitwise AND &
                                ida_hexrays.cot_bor,      # Bitwise OR |
                                ida_hexrays.cot_xor,      # Bitwise XOR ^

                                # Bit operations
                                ida_hexrays.cot_shl,      # Shift left <<
                                ida_hexrays.cot_sshr,      # s.Logical shift right >>
                                ida_hexrays.cot_ushr,      # u.Logical shift right >>

                                # Cast operations
                                ida_hexrays.cot_cast,     # Type casting

                                # Ternary operator
                                ida_hexrays.cot_tern,      # Ternary ? :

                                # Helper operations
                                ida_hexrays.cot_helper,   # Helper function call
                                ida_hexrays.cot_sizeof    # sizeof operator
                            ]:
                                self.is_used = True
                return 0

            def find_parent_expr(self, target_expr):
                """Find the parent expression that contains the given expression"""
                class ParentFinder(ida_hexrays.ctree_visitor_t):
                    def __init__(self, target_expr):
                        ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                        self.target_expr = target_expr
                        self.parent = None

                    def compare_expr(self, e1, e2):
                        """Safely compare two expressions"""
                        if e1 is None or e2 is None:
                            return False
                        try:
                            # Compare basic properties
                            return (e1.op == e2.op and
                                    e1.ea == e2.ea and
                                    (hasattr(e1, 'v') and hasattr(e2, 'v') and
                                    e1.v.idx == e2.v.idx))
                        except:
                            return False

                    def visit_expr(self, e):
                        try:
                            # Check if any of the operands is our target
                            if hasattr(e, 'x') and self.compare_expr(e.x, self.target_expr):
                                self.parent = e
                                return 1
                            if hasattr(e, 'y') and self.compare_expr(e.y, self.target_expr):
                                self.parent = e
                                return 1
                            if hasattr(e, 'z') and self.compare_expr(e.z, self.target_expr):
                                self.parent = e
                                return 1
                            # For function calls, check arguments
                            if e.op == ida_hexrays.cot_call and hasattr(e, 'a'):
                                for arg in e.a:
                                    if self.compare_expr(arg, self.target_expr):
                                        self.parent = e
                                        return 1
                        except:
                            pass
                        return 0

                finder = ParentFinder(target_expr)
                finder.apply_to(self.cfunc.body, None)
                return finder.parent

        checker = UsageChecker(cfunc)
        checker.apply_to(cfunc.body, None)
        return checker.is_used

    def _is_register_used_in_virtual_call(self, cfunc, reg, param_names):
        """
        Checks if a register is used ONLY in virtual function calls.
        """
        logger = DebugLogger.get_instance()
        if not cfunc or not hasattr(cfunc, 'body'):
            logger.debug("No function body available")
            return False

        class VirtualCallChecker(ida_hexrays.ctree_visitor_t):
            def __init__(self, reg, cfunc, param_names):
                ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                self.reg = reg
                self.cfunc = cfunc
                self.found_in_virtual_call = False
                self.found_in_normal_usage = False
                self.param_map = {}
                self.param_name = None


                if self.reg.lower() in param_names:
                    self.param_map[param_names[self.reg.lower()]] = self.reg.lower()
                    self.param_name = param_names[self.reg.lower()]

                logger.debug(f"Checking for parameter: {self.param_map},  target: {self.reg}")

            def _check_var_name(self, lvar):
                return hasattr(lvar, 'name') and lvar.name in self.param_map

            def _check_expr_for_param(self, expr, context="", in_virtual_call=False):
                """Recursively check expression for parameter usage"""
                if not expr:
                    return False

                try:
                    if expr.op == ida_hexrays.cot_var:
                        lvar = self.cfunc.get_lvars()[expr.v.idx]
                        if self._check_var_name(lvar):
                            reg_name = self.param_map[lvar.name]
                            if in_virtual_call:
                                logger.debug(
                                    f"Found parameter {lvar.name} ({reg_name}) in virtual call {context}"
                                )
                                self.found_in_virtual_call = True
                            else:
                                logger.debug(
                                    f"Found parameter {lvar.name} ({reg_name}) in normal usage {context}"
                                )
                                # Check if the normal usage is within a virtual call context
                                is_within = self.is_within_virtual_call_context(expr)
                                if not is_within:
                                    self.found_in_normal_usage = True
                                
                            return True

                    # Check sub-expressions
                    if hasattr(expr, "x") and expr.x:
                        if self._check_expr_for_param(expr.x, context, in_virtual_call):
                            return True
                    if hasattr(expr, "y") and expr.y:
                        if self._check_expr_for_param(expr.y, context, in_virtual_call):
                            return True
                    if hasattr(expr, "a") and expr.a:  # Check arguments of a call
                        for i, arg in enumerate(expr.a):
                            if arg:
                                if self._check_expr_for_param(
                                    arg, f"argument {i}", in_virtual_call
                                ):
                                    return True

                except Exception as ex:
                    logger.debug(f"Error in _check_expr_for_param: {str(ex)}")

                return False

            def is_within_virtual_call_context(self, expr):
                """
                Checks if the given expression is part of a virtual call context.
                """
                class ParentFinder(ida_hexrays.ctree_visitor_t):
                        def __init__(self, target_expr):
                            ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                            self.target_expr = target_expr
                            self.parent = None

                        def compare_expr(self, e1, e2):
                            """Safely compare two expressions"""
                            if e1 is None or e2 is None:
                                return False
                            try:
                                # Compare basic properties
                                return (e1.op == e2.op and
                                        e1.ea == e2.ea and
                                        (hasattr(e1, 'v') and hasattr(e2, 'v') and
                                        e1.v.idx == e2.v.idx))
                            except:
                                return False
                            
                        def visit_expr(self, e):
                            try:
                                # Check if any of the operands is our target
                                if hasattr(e, 'x') and self.compare_expr(e.x, self.target_expr):
                                    self.parent = e
                                    return 1
                                if hasattr(e, 'y') and self.compare_expr(e.y, self.target_expr):
                                    self.parent = e
                                    return 1
                                if hasattr(e, 'z') and self.compare_expr(e.z, self.target_expr):
                                    self.parent = e
                                    return 1
                                # For function calls, check arguments
                                if e.op == ida_hexrays.cot_call and hasattr(e, 'a'):
                                    for arg in e.a:
                                        if self.compare_expr(arg, self.target_expr):
                                            self.parent = e
                                            return 1
                            except:
                                pass
                            return 0

                parent_finder = ParentFinder(expr)
                parent_finder.apply_to(self.cfunc.body, None)
                parent = parent_finder.parent
                
                while parent:
                    if parent.op == ida_hexrays.cot_call:
                        if self._is_potential_vtable_access(parent.x):
                            return True
                    elif parent.op == ida_hexrays.cot_asg:
                        # If it's an assignment, check the right-hand side
                        if parent.x == expr:  # Ensure we are on the left side of assignment
                            if hasattr(parent, 'y'):
                                if self._check_expr_for_param(parent.y, "assignment RHS", True):
                                    return True
                    elif parent.op in [ida_hexrays.cot_memptr, ida_hexrays.cot_memref]:
                        if self._is_potential_vtable_access(parent):
                            return True

                    parent_finder = ParentFinder(parent)
                    parent_finder.apply_to(self.cfunc.body, None)
                    parent = parent_finder.parent

                return False

            def _is_potential_vtable_access(self, expr):
                """Check various vtable access patterns"""
                if not expr:
                    return False

                try:
                    # Pattern 1: Direct pointer dereference
                    if expr.op == ida_hexrays.cot_ptr:
                        return True

                    # Pattern 2: Member pointer access
                    if expr.op == ida_hexrays.cot_memptr:
                        return True

                    # Pattern 3: Addition operation (possible vtable offset)
                    if expr.op == ida_hexrays.cot_add:
                        if hasattr(expr, 'x') and expr.x:
                            if expr.x.op == ida_hexrays.cot_ptr:
                                return True
                            if self._check_expr_for_param(expr.x, "vtable base", True):
                                return True

                    # Pattern 4: Cast operations
                    if expr.op == ida_hexrays.cot_cast:
                        return self._is_potential_vtable_access(expr.x if hasattr(expr, 'x') else None)

                except Exception as ex:
                    logger.debug(f"Error in _is_potential_vtable_access: {str(ex)}")

                return False

            def visit_expr(self, e):
                try:
                    if e.op == ida_hexrays.cot_call:
                        # Check if this is potentially a virtual call
                        is_virtual = False
                        if hasattr(e, 'x') and e.x:
                            is_virtual = self._is_potential_vtable_access(e.x)
                            if is_virtual:
                                logger.debug("Found potential virtual call pattern")
                                # Check the call expression itself
                                self._check_expr_for_param(e.x, "call expression", True)

                            # Check arguments
                            if hasattr(e, 'a') and e.a:
                                for i, arg in enumerate(e.a):
                                    if not arg:
                                        continue
                                    self._check_expr_for_param(arg, f"argument {i}", is_virtual)

                    # Check normal usage (non-call expressions)
                    elif e.op in [ida_hexrays.cot_asg, ida_hexrays.cot_idx,
                                ida_hexrays.cot_add, ida_hexrays.cot_sub,
                                ida_hexrays.cot_mul,
                                ida_hexrays.cot_sdiv, ida_hexrays.cot_udiv, # Possible division opcodes
                                ida_hexrays.cot_eq, ida_hexrays.cot_ne,
                                ida_hexrays.cot_slt, ida_hexrays.cot_ult, # Added cot_slt, cot_ult for signed/unsigned
                                ida_hexrays.cot_sle, ida_hexrays.cot_ule, # Added cot_sle, cot_ule for signed/unsigned
                                ida_hexrays.cot_sgt, ida_hexrays.cot_ugt, # Added cot_sgt, cot_ugt for signed/unsigned
                                ida_hexrays.cot_sge, ida_hexrays.cot_uge, # Added cot_sge, cot_uge for signed/unsigned
                                ida_hexrays.cot_land, ida_hexrays.cot_lor]:
                        self._check_expr_for_param(e, "normal operation", False)

                    # Also check member access expressions
                    elif e.op in [ida_hexrays.cot_memptr, ida_hexrays.cot_memref]:
                        if self._is_potential_vtable_access(e):
                            self._check_expr_for_param(e.x, "member access", True)
                        else:
                            self._check_expr_for_param(e.x, "normal member access", False)

                except Exception as ex:
                    logger.debug(f"Error in visit_expr: {str(ex)}")

                return 0

        checker = VirtualCallChecker(reg, cfunc, param_names)
        checker.apply_to(cfunc.body, None)

        logger.debug(f"Parameter: {reg}")
        logger.debug(f"Found in virtual calls: {checker.found_in_virtual_call}")
        logger.debug(f"Found in normal usage: {checker.found_in_normal_usage}")

        result = checker.found_in_virtual_call and not checker.found_in_normal_usage
        logger.debug(f"Final virtual call check result for {reg}: {result}")

        return result

    def _determine_calling_convention(self, func_ea, reg_specs,stack_specs, cfunc,param_names):
        """Determine the appropriate calling convention based on register specifications, stack specs and architecture"""
        logger = DebugLogger.get_instance()
        if not reg_specs:
            return ida_typeinf.CM_CC_CDECL

        # Debug for all register information
        logger.debug(f"Analyzing function at {hex(func_ea)}")
        logger.debug(f"Register specifications: {reg_specs}")
        logger.debug(f"Architecture: {'x64' if self.is_64bit() else 'x86'}")
        logger.debug(f"Stack specifications: {stack_specs}")
        
        # Analyze register usage
        param_regs = []
        return_reg = None
        register_usage = {
            'ecx_used': False,
            'rcx_used': False,
            'xmm0_used': False,
            'xmm1_used': False,
            'ebp_used' : False,
            'esp_used' : False,
        }
        
        # For first parameter tracking
        first_param = None
        param_count = 0
        
        virtual_call_registers = []

        for spec_type, reg in reg_specs:
            reg = reg.lower()
            logger.debug(f"Processing {spec_type}: {reg}")

            if spec_type == 'param':
                if param_count == 0:
                    first_param = reg
                param_count += 1
                
                param_regs.append(reg)
                
                # Save register usage status
                if reg == 'ecx':
                    register_usage['ecx_used'] = True
                    logger.debug("Found ecx parameter")
                elif reg == 'rcx':
                    register_usage['rcx_used'] = True
                    logger.debug("Found rcx parameter")
                elif reg in ['xmm0', 'xmm1']:
                    register_usage[f'{reg}_used'] = True
                    logger.debug(f"Found {reg} parameter")
                elif reg in ["ebp","esp"]:
                    register_usage[f'{reg}_used'] = True
                elif reg not in ['ecx','rcx','xmm0','xmm1','ebp','esp']:
                    virtual_call_registers.append(reg)
            elif spec_type == 'return':
                return_reg = reg

        # x64 architecture check
        if self.is_64bit():
            logger.debug("Processing x64 architecture rules")
            
            # Microsoft x64 calling convention (always fastcall)
            # RCX, RDX, R8, R9 or XMM0-XMM3 usage
            if (register_usage['rcx_used'] or 
                register_usage['xmm0_used'] or 
                first_param in ['rcx', 'xmm0']):
                logger.debug(f"Function at {hex(func_ea)} marked as x64 fastcall")
                return ida_typeinf.CM_CC_FASTCALL
                
        # x86 architecture check
        else:
            logger.debug("Processing x86 architecture rules")
            
            # Thiscall check (ECX parameter)
            if register_usage['ecx_used']:
                logger.debug(f"Function at {hex(func_ea)} marked as thiscall due to ecx parameter")
                return ida_typeinf.CM_CC_THISCALL
            if register_usage['ebp_used'] or register_usage['esp_used']:
                logger.debug(f"Function at {hex(func_ea)} marked as cdecl due to stack parameter")
                return ida_typeinf.CM_CC_CDECL
            
            # x86 fastcall check
            # First parameter ECX/XMM0 or certain register combinations
            if (first_param in ['ecx', 'xmm0'] or 
                register_usage['xmm0_used']):
                logger.debug(f"Function at {hex(func_ea)} marked as x86 fastcall")
                return ida_typeinf.CM_CC_FASTCALL
            
            # Virtual call check
            # If ebx, edi, etc. registers are used and followed by stack parameters

            if virtual_call_registers or stack_specs:
                                              
                for reg in virtual_call_registers:
                        if self._is_register_used_in_virtual_call(cfunc,reg,param_names):
                           logger.debug(f"Function at {hex(func_ea)} marked as cdecl due to register {reg} in virtual call and stack parameters.")
                           return ida_typeinf.CM_CC_CDECL
                        
                    
       
        logger.debug(f"Function at {hex(func_ea)} defaulting to usercall")
        return ida_typeinf.CM_CC_SPECIAL

    def get_function_type(self, func_ea):
        """Get function type from both decompiler and IDA database"""
        # First try to get type from decompiler
        try:
            ida_hexrays.mark_cfunc_dirty(func_ea, False)
            cfunc = ida_hexrays.decompile(func_ea)
            if cfunc:
                func_type = str(cfunc.type.dstr())
                return func_type, cfunc  # Return both func_type and cfunc
        except:
            pass

        # If decompiler fails, try to get from IDA database
        func_type = idc.get_type(func_ea)
        return func_type, None  # Return func_type and None if decompiler fails

    #
    def parse_register_specs(self, func_type, cfunc):
        """Parse register specifications from function type string and cfunc arguments"""
        reg_specs = []
        stack_specs = []
        param_names = {}
        try:
            if not cfunc:  # If cfunc is None, we can't use cfunc.arguments
                if not func_type or '@<' not in func_type:
                    return None, None, None
            else:
                # Parse parameters from cfunc.arguments
                for j, arg in enumerate(cfunc.arguments):
                    if not arg.name:
                        continue
                    if arg.is_stk_var():
                        offset = cfunc.mba.stacksize - arg.location.stkoff()
                        stack_specs.append((arg.name, f"0x{abs(offset):x}"))
                    elif arg.is_reg_var():
                        regnum = arg.get_reg1()
                        register_name = idaapi.get_mreg_name(regnum, arg.width)
                        reg_specs.append(('param', register_name))
                        param_names[register_name.lower()] = arg.name

                if func_type and '@<' in func_type:
                    parts = func_type.split('@<')
                    # Check for return register in first part
                    return_reg = None
                    if len(parts) > 1:
                        return_part = parts[0]
                        if return_part.endswith('__userpurge') or return_part.endswith('__usercall'):
                            return_reg = parts[1].split('>')[0]
                            reg_specs.append(('return', return_reg))
                return reg_specs if reg_specs else None, stack_specs if stack_specs else None, param_names if param_names else None

        except Exception as e:
            print(f"Error parsing register specs: {str(e)}")
            return None, None, None

    def is_imported_function(self, func_ea):
        """Check if the function is imported/thunk"""

        # 1. Get flags
        flags = ida_bytes.get_flags(func_ea)

        # If it's not a function, return False
        if not ida_bytes.is_func(flags):
            return False

        func = ida_funcs.get_func(func_ea)
        if func:
            # 2. Check if it has FUNC_THUNK or FUNC_LIB flag
            if func.flags & (ida_funcs.FUNC_THUNK | ida_funcs.FUNC_LIB):
                return True

        # 5. Check if the function name starts with one of the import prefixes
        func_name = idc.get_func_name(func_ea)
        import_prefixes = ['__imp_', 'j_', 'imp_', '_imp']
        if any(func_name.startswith(prefix) for prefix in import_prefixes):
            return True

        # 6. Check if the demangled name starts with one of the import prefixes
        demangled_name = idc.demangle_name(func_name, idc.get_inf_attr(idc.INF_SHORT_DN))
        if demangled_name and any(demangled_name.startswith(prefix) for prefix in import_prefixes):
            return True

        # If it's not an import, return False
        return False

    def has_usercall_convention(self, func_ea):
        """Enhanced check for __usercall/__userpurge convention"""
        logger = DebugLogger.get_instance()
        func_type, cfunc = self.get_function_type(func_ea)
        if func_type:
            # Check for __usercall or __userpurge keywords
            if ("__usercall" in func_type) or ("__userpurge" in func_type):
                reg_specs, stack_specs , param_names = self.parse_register_specs(func_type,cfunc)
                if reg_specs:
                    logger.debug(f"Found register specifications for {hex(func_ea)}: {reg_specs}")
                    self.print_function_info(func_ea)
                return True

            # Check for register specifications in function type
            if '@<' in func_type:
                reg_specs, stack_specs , param_names = self.parse_register_specs(func_type,cfunc)
                if reg_specs:
                    logger.debug(f"Found register specifications for {hex(func_ea)}: {reg_specs}")
                    return True

        return False

    def fix_calling_convention(self, func_ea):
        # Load config
        logger = DebugLogger.get_instance()
        config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)
        except:
            return

        if not config.get("func_type_analysis", True):
            return

        if config.get("show_debug", False):
            logger.debug(f"\nAnalyzing function type at {hex(func_ea)}")

        def debug_print(msg):
            if config["show_debug"]:
                print(f"[DEBUG] {msg}")

        if func_ea in self.processed_funcs:
            debug_print(f"Function {hex(func_ea)} already processed")
            return

        if self.is_imported_function(func_ea):
            debug_print(f"Skipping imported function at {hex(func_ea)}")
            return

        self.processed_funcs.add(func_ea)

        try:
            # Get function type from decompiler first
            func_type, cfunc = self.get_function_type(func_ea)
            if not func_type:
                logger.debug(f"No type information available for function at {hex(func_ea)}")
                return

            logger.debug(f"\nProcessing function at {hex(func_ea)}")
            logger.debug(f"Original function type: {func_type}")

            if self.has_usercall_convention(func_ea):
                # Parse register specifications
                reg_specs, stack_specs, param_names = self.parse_register_specs(func_type, cfunc)

                if not reg_specs:
                    logger.debug(f"No register specifications found for function at {hex(func_ea)}")
                    return

                logger.debug(f"Found register specs: {reg_specs}")

                # Determine calling convention if direct conversion is not possible
                new_cc = self._determine_calling_convention(func_ea, reg_specs, stack_specs, cfunc, param_names)
                logger.debug(f"Determined calling convention: {new_cc}")

                # Try to decompile to get current function details
                ida_hexrays.mark_cfunc_dirty(func_ea, False)
                cfunc = ida_hexrays.decompile(func_ea)
                if not cfunc:
                    logger.debug(f"Failed to decompile function at {hex(func_ea)}")
                    return

                # Get type info from decompiler
                tinfo = cfunc.type
                func_details = ida_typeinf.func_type_data_t()
                if tinfo.get_func_details(func_details):
                    # Set the determined calling convention
                    func_details.cc = new_cc

                    # Create and apply the new type
                    tinfo.create_func(func_details)
                    if ida_typeinf.apply_tinfo(func_ea, tinfo, ida_typeinf.TINFO_DEFINITE):
                        logger.debug(f"Successfully applied new calling convention at {hex(func_ea)}")

                        # Remove unused parameters
                        self._analyze_and_remove_unused_parameters(func_ea)
                        if config["unused_param_analysis"]:
                            self._analyze_calls_and_update_signature(func_ea)

                        # Refresh view if in pseudocode window
                        if config["auto_refresh_views"]:
                            widget_name = f"Pseudocode-{idc.get_func_name(func_ea)}"
                            vu = ida_hexrays.get_widget_vdui(ida_kernwin.find_widget(widget_name))
                            if vu:
                                vu.refresh_view(True)
                    else:
                        logger.debug(f"Failed to apply type at {hex(func_ea)}")
            else:
                logger.debug(f"Skipping function at {hex(func_ea)} as it does not use usercall convention")

        except Exception as e:
            logger.debug(f"Error processing function at {hex(func_ea)}: {str(e)}")

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

    def activate(self, ctx):
        if not ida_hexrays.init_hexrays_plugin():
            print("Hex-rays decompiler is not available!")
            return 0

        vu = ida_hexrays.get_widget_vdui(ctx.widget)
        if not vu:
            print("No pseudocode view available!")
            return 0

        current_function = vu.cfunc.entry_ea
        if not current_function:
            print("No function at current address!")
            return 0

        self.process_function_backwards(current_function)
        return 1

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

    def get_calls_in_function(self, func_ea):
        """
        Finds calls in a function using either the decompiler or IDA API.
        """
        logger = DebugLogger.get_instance()
        calls = []
        config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
        try:
            with open(config_path, 'r') as f:
                config = json.load(f)
        except Exception as e:
            logger.debug(f"Config file not found {config_path}: {e}")
            return calls

        search_engine = config.get("search_engine", "IDA-API")

        try:
            if search_engine == "Hex-Rays":
                # Decompile the function
                ida_hexrays.mark_cfunc_dirty(func_ea, False)
                cfunc = ida_hexrays.decompile(func_ea)
                if not cfunc:
                    logger.debug(f"Failed to decompile function at {hex(func_ea)}.")
                    return calls

                class CallVisitor(ida_hexrays.ctree_visitor_t):
                    def __init__(self, calls):
                        ida_hexrays.ctree_visitor_t.__init__(self, ida_hexrays.CV_FAST)
                        self.calls = calls

                    def visit_expr(self, expr):
                        if expr.op == ida_hexrays.cot_call:
                            try:
                                if expr.x.op != ida_hexrays.cot_obj:
                                    return 0
                                if expr.x.obj_ea == idaapi.BADADDR:
                                    return 0
                                target_ea = expr.x.obj_ea
                                if target_ea not in self.calls and ida_funcs.get_func(target_ea):
                                    self.calls.append(target_ea)
                            except Exception as e:
                                logger.debug(f"Error during visit_expr: {str(e)}")
                        return 0

                visitor = CallVisitor(calls)
                visitor.apply_to(cfunc.body, None)
                return calls

            elif search_engine == "IDA-API":
                func = ida_funcs.get_func(func_ea)
                if not func:
                    return calls

                # Iterate over each instruction in the function
                func_items = list(idautils.FuncItems(func_ea))
                for ea in reversed(func_items):
                    if idaapi.is_call_insn(ea) or idc.print_insn_mnem(ea) == "jmp":
                        call_target = idc.get_operand_value(ea, 0)
                        # Add the call target if it has not been added before and is a valid function
                        if call_target not in calls and ida_funcs.get_func(call_target):
                            calls.append(call_target)
                return calls

            else:
                logger.debug(f"Invalid search engine selected: {search_engine}")
                return calls
        except Exception as e:
            logger.debug(f"Error getting calls in function at {hex(func_ea)}: {str(e)}")
            return calls

    
    def collect_layered_call_chain(self, func_ea, max_layers, current_layer=1, visited=None, call_chain=None):
        """Collects a layered call chain
        
        Args:
            func_ea: Starting function address
            max_layers: Maximum layer count (default: 2)
            current_layer: Current layer count
            visited: Set of visited functions
            call_chain: Collected call chain
        """
        logger = DebugLogger.get_instance()
        if visited is None:
            visited = set()
        if call_chain is None:
            call_chain = []
            
        # If the function has already been visited or the maximum layer count has been reached, stop
        if func_ea in visited or current_layer > max_layers:
            return call_chain

        visited.add(func_ea)
        
        # Collect calls in the current layer
        current_layer_calls = []
        func = ida_funcs.get_func(func_ea)
        if func:
                
                calls = self.get_calls_in_function(func_ea)
                for target in calls:
                    
                    current_layer_calls.append({
                                'caller_ea': func_ea,
                                'call_ea': target,
                                'target_ea': target,
                                'layer': current_layer,
                                'processed': False
                            })
        
        # Reverse the calls in the current layer
        current_layer_calls.reverse()
        
        # Add the calls in the current layer to the chain
        for call in current_layer_calls:
            call_chain.append(call)
            
            # Move to the next layer
            if current_layer < max_layers:
                self.collect_layered_call_chain(
                    call['target_ea'],
                    max_layers,
                    current_layer + 1,
                    visited,
                    call_chain
                )
        
        # Add the starting function to the chain (only for the first layer)
        if current_layer == 1:
            call_chain.append({
                'caller_ea': func_ea,
                'call_ea': func_ea,
                'target_ea': func_ea,
                'layer': 1,
                'processed': False,
                'is_start_function': True
            })
        
        return call_chain



    def process_function_backwards(self, start_ea):
        logger = DebugLogger.get_instance()
        
        with ProgressDialog("Initializing analysis... (Press Esc to cancel)") as progress:
            try:
                # Load config
                config_path = os.path.join(ida_diskio.get_user_idadir(), "retrospective_config.json")
                try:
                    with open(config_path, 'r') as f:
                        config = json.load(f)
                        logger.set_enabled(config.get("show_debug", False))
                except:
                    config = {
                        "max_layers": 4,
                        "auto_refresh_views": True,
                        "show_debug": False,
                        "param_analysis": True,
                        "func_type_analysis": True,
                        "unused_param_analysis": True,
                        "virtual_call_analysis" : True,
                        "search_engine" : "IDA-API"
                    }

                logger.debug(f"Starting backwards analysis from {hex(start_ea)}")
                func = ida_funcs.get_func(start_ea)
                
                if config["auto_refresh_views"]:
                    ida_hexrays.mark_cfunc_dirty(func.start_ea)

                # Phase 1
                progress.replace_message("Phase 1: Collecting call chain... (Press Esc to cancel)")
                logger.debug("Phase 1: Collecting layered call chain...")
                
                if progress.check_cancelled():
                    print("\nOperation cancelled by user during call chain collection.")
                    return
                    
                call_chain = self.collect_layered_call_chain(start_ea, max_layers=config["max_layers"])
                
                # Phase 2
                progress.replace_message("Phase 2: Processing functions... (Press Esc to cancel)")
                logger.debug("\nPhase 2: Processing functions in reverse order...")
                fixer = CallingConventionFixer()
                
                max_layer = max(call['layer'] for call in call_chain)
                for current_layer in range(max_layer, 0, -1):
                    progress.replace_message(f"Processing Layer {current_layer} of {max_layer}... (Press Esc to cancel)")
                    logger.debug(f"\nProcessing Layer {current_layer}:")
                    
                    if progress.check_cancelled():
                        print(f"\nOperation cancelled by user at layer {current_layer}.")
                        return
                    
                    layer_calls = [call for call in call_chain if call['layer'] == current_layer]
                    for i, call in enumerate(layer_calls, 1):
                        try:
                            target_ea = call['target_ea']
                            caller_ea = call['caller_ea']
                            call_ea = call['call_ea']
                            
                            progress.replace_message(
                                f"Layer {current_layer}/{max_layer}: Processing function {i}/{len(layer_calls)} at {hex(target_ea)}... "
                                "(Press Esc to cancel)"
                            )
                            
                            if progress.check_cancelled():
                                print(f"\nOperation cancelled by user while processing function at {hex(target_ea)}.")
                                return
                            
                            logger.debug(f"\nProcessing call at {hex(call_ea)}:")
                            logger.debug(f"  From: {hex(caller_ea)}")
                            logger.debug(f"  To: {hex(target_ea)}")
                            
                            fixer.fix_calling_convention(target_ea)
                            if config["param_analysis"]:
                                fixer._analyze_and_remove_unused_parameters(target_ea)
                            else:
                                if config["func_type_analysis"]:
                                 fixer.fix_calling_convention(target_ea)
                                    
                            if config["auto_refresh_views"]:
                                func_name = idc.get_func_name(target_ea)
                                vu = ida_hexrays.get_widget_vdui(ida_kernwin.find_widget(f"Pseudocode-{func_name}"))
                                if vu:
                                    vu.refresh_view(True)
                                    
                        except Exception as e:
                            logger.debug(f"Error processing call: {str(e)}")
                            continue
                            
                        if progress.check_cancelled():
                            print(f"\nOperation cancelled by user during function processing.")
                            return
                
                # Phase 3: Virtual Call Type Analysis
                progress.replace_message("Phase 3: Analyzing virtual call types... (Press Esc to cancel)")
                logger.debug("\nPhase 3: Analyzing virtual call types...")
                
                if progress.check_cancelled():
                    print(f"\nOperation cancelled by user during virtual call analysis.")
                    return
                
                for current_layer in range(max_layer, 0, -1):
                    progress.replace_message(f"Processing Layer {current_layer} for virtual calls of {max_layer}... (Press Esc to cancel)")
                    logger.debug(f"\nProcessing Layer {current_layer}:")
                    
                    if progress.check_cancelled():
                        print(f"\nOperation cancelled by user at layer {current_layer}.")
                        return
                    
                    layer_calls = [call for call in call_chain if call['layer'] == current_layer]
                    for i, call in enumerate(layer_calls, 1):
                            try:
                                target_ea = call['target_ea']
                                caller_ea = call['caller_ea']
                                call_ea = call['call_ea']
                                
                                progress.replace_message(
                                    f"Layer {current_layer}/{max_layer}: Processing virtual calls for function {i}/{len(layer_calls)} at {hex(target_ea)}... "
                                    "(Press Esc to cancel)"
                                )
                            
                                if progress.check_cancelled():
                                    print(f"\nOperation cancelled by user while processing virtual call types at {hex(target_ea)}.")
                                    return
                                
                                logger.debug(f"\nAnalyzing virtual calls at {hex(call_ea)}:")
                                logger.debug(f"  From: {hex(caller_ea)}")
                                logger.debug(f"  To: {hex(target_ea)}")
                                
                                if config["virtual_call_analysis"]:
                                   fixer._analyze_virtual_calls_and_update_signature(target_ea)
                                else:
                                    logger.debug(f"Skipping virtual call analysis for {hex(target_ea)}")
                            
                                if config["auto_refresh_views"]:
                                   func_name = idc.get_func_name(target_ea)
                                   vu = ida_hexrays.get_widget_vdui(ida_kernwin.find_widget(f"Pseudocode-{func_name}"))
                                   if vu:
                                       vu.refresh_view(True)
                                       
                            except Exception as e:
                                logger.debug(f"Error processing virtual calls at {hex(target_ea)}: {str(e)}")
                                continue
                                
                            if progress.check_cancelled():
                                    print(f"\nOperation cancelled by user during virtual calls processing.")
                                    return
                
                progress.replace_message("Analysis completed successfully!")
                print("\nAnalysis completed successfully!")
                
            except Exception as e:
                print(f"\nError during analysis: {str(e)}")
            finally:
                if progress.check_cancelled():
                    print("\nOperation was cancelled by user.")

class BackwardsDecompilerHooks(idaapi.UI_Hooks):
    def finish_populating_widget_popup(self, form, popup):
        widget_type = idaapi.get_widget_type(form)

        if widget_type == idaapi.BWN_DISASM or widget_type == idaapi.BWN_PSEUDOCODE:
            idaapi.attach_action_to_popup(form, popup, "Retrospective_analysis:run")

class BackwardsDecompilerPlugin(idaapi.plugin_t):
  flags = idaapi.PLUGIN_KEEP
  comment = "Retrospective Analysis Plugin"
  help = "Retrospective Analysis Plugin for IDA Pro"
  wanted_name = "Retrospective Analysis"
  wanted_hotkey = ""

  def __init__(self):
      super().__init__()
      self.action_name = "Retrospective_analysis:run"
      self.config_action_name = "Retrospective_analysis:config"
      self.hooks = None

  def init(self):
        if not ida_hexrays.init_hexrays_plugin():
            print("Hex-rays decompiler is not available.")
            return idaapi.PLUGIN_SKIP

        # Register menu items
        menu_path = "Edit/Plugins/Retrospective Analysis/"
        
        # Register actions
        if not idaapi.register_action(idaapi.action_desc_t(
            self.action_name,
            "Retrospective analysis",
            BackwardsDecompilerHandler(),
            None,
            "Retrospective analysis",
            -1)):
            print("Failed to register run action")
            return idaapi.PLUGIN_SKIP

        if not idaapi.register_action(idaapi.action_desc_t(
            self.config_action_name,
            "Configure",
            ConfigActionHandler(),
            None,
            "Configure settings",
            -1)):
            print("Failed to register config action")
            return idaapi.PLUGIN_SKIP

        # Attach actions to menu
        if not idaapi.attach_action_to_menu(f"{menu_path}Retrospective analysis", self.action_name, idaapi.SETMENU_APP):
            print("Failed to attach run action")
            
        if not idaapi.attach_action_to_menu(f"{menu_path}Configure", self.config_action_name, idaapi.SETMENU_APP):
            print("Failed to attach config action")

        # Setup hooks
        self.hooks = BackwardsDecompilerHooks()
        self.hooks.hook()
        first_config(self)

        print("Retrospective analysis plugin initialized successfully")
        return idaapi.PLUGIN_KEEP

  def run(self, arg):
      show_config_dialog()

  def term(self):
      try:
          if self.hooks:
              self.hooks.unhook()
          idaapi.unregister_action(self.action_name)
          idaapi.unregister_action(self.config_action_name)
      except:
          pass

def PLUGIN_ENTRY():
    try:
        return BackwardsDecompilerPlugin()
    except Exception as e:
        print(f"Error loading Backwards Decompiler plugin: {str(e)}")
        return None


repository: stong/ida-scripts
file: force-lvar-width.py
description: My IDA Pro scripts. (Not all created by me)
wanted_name: Hex-Rays lvar width forcer
comment: Force lvar width plugin for Hex-Rays decompiler
created_at: 2021-09-18 18:59:25+00:00
pushed_at: 2021-09-18 18:59:46+00:00
url:
https://github.com/stong/ida-scripts/blob/7fd169cb4b0e137299c9d6b3ac230abc0591ec7b/force-lvar-width.py

content:
""" Forcibly change the width of an lvar.
Useful for making an lvar smaller (which Hex-Rays does not let you do apparently)
Tags (for search engines):
IDA Pro Hex-Rays make stack variable smaller force variable size decrease width delete variable split up break up variable into smaller
"""

import idautils
import idaapi
import idc
import ida_hexrays
import ida_kernwin

import traceback

force_width_actname = "forcelvarwidth:forcewidth"

class force_width_action_handler_t(idaapi.action_handler_t):
    def __init__(self, callback_info):
        idaapi.action_handler_t.__init__(self)
        self.callback_info = callback_info

    def activate(self, ctx):
        vdui = idaapi.get_widget_vdui(ctx.widget)
        self.callback_info.gui_action_callback(vdui)
        return 1

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


class hexrays_callback_info(object):

    def __init__(self):
        self.vu = None
        return

    def load(self):
        return

    def save(self):
        return

    def do_force_width(self, cfunc, insn):

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

        cif = insn.details

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

        idaapi.qswap(cif.ithen, cif.ielse)
        cond = idaapi.cexpr_t(cif.expr)
        notcond = idaapi.lnot(cond)

        cif.expr.swap(notcond)

        return True

    def gui_action_callback(self, vu):

        cfunc = vu.cfunc.__deref__()
        
        if not vu.get_current_item(idaapi.USE_KEYBOARD):
            print("Force lvar width: you don't have anything selected")
            return False
            
        badlv = vu.item.get_lvar()
        if not badlv:
            print("Force lvar width: you don't have an lvar selected")
            return False

        new_width = ida_kernwin.ask_long(badlv.width, "Enter the new width for " + badlv.name)
        if new_width == None: # cancelled
            print("Force lvar width: operation cancelled")
            return False

        if new_width <= 0:
            print("Force lvar width: not allowed. Non-positive width will crash IDA")
            return False

        badlv.set_width(new_width)
        print('Set the type in IDA (Y) for it to apply')
        idaapi.process_ui_action('hx:SetType')

        # vu.refresh_ctext()
        print('Force lvar width: OK.')
        return True

    def event_callback(self, event, *args):

        if event == idaapi.hxe_populating_popup:
            widget, phandle, vu = args
            res = idaapi.attach_action_to_popup(vu.ct, None, force_width_actname)

        return 0

class ForceLvarWidth(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE
    wanted_hotkey = "Shift-W"
    comment = "Force lvar width plugin for Hex-Rays decompiler"
    help = "There is no one to help you now"
    wanted_name = "Hex-Rays lvar width forcer"

    def init(self):
        if idaapi.init_hexrays_plugin():
            i = hexrays_callback_info()
            idaapi.register_action(
                idaapi.action_desc_t(
                    force_width_actname,
                    "Force lvar width",
                    force_width_action_handler_t(i),
                    "Shift-W"))
            idaapi.install_hexrays_callback(i.event_callback)
            print('Hex-Rays lvar width forcer by ecx86 loaded!')
        else:
            print('Force lvar width: Hexrays is not available.')

    def term(self):
        pass

    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return ForceLvarWidth()


repository: LemonHaze420/misc_utils
file: lemonyfresh.py
description: misc re tools
wanted_name: Lemonyfresh
comment: Hexrays struct fixer upper
created_at: 2025-02-08 23:34:27+00:00
pushed_at: 2025-04-21 18:39:09+00:00
url:
https://github.com/LemonHaze420/misc_utils/blob/5f3700c4375542dc696f5855569944810d9a9a7a/lemonyfresh.py

content:
'''
IDA 7.4+ Python 3 IDA Plugin for hexrays struct fixer uppering
Dedicated to Lemon Haze
SocraticBliss (R) + LemonHaze (c) >:(
'''
import re
import idaapi

from idaapi import stroff_flag, off_flag

class lemonyfresh_t(idaapi.plugin_t):
    flags         = idaapi.PLUGIN_PROC
    comment       = "Hexrays struct fixer upper"
    help          = "When life gives you lemons, don't make lemonade! Make life take the lemons back! Get mad! I don't want your damn lemons!"
    wanted_name   = "Lemonyfresh"
    wanted_hotkey = "Alt-F12"
    
    def init(self):
        # We only want to load the plugin if hexrays is available
        if not idaapi.init_hexrays_plugin():
            return idaapi.PLUGIN_SKIP
        else:
            '''
            # Register an action for right click events
            idaapi.install_hexrays_callback(right_click)
            desc = idaapi.action_desc_t()
            desc.name     = "lhz:InsertNewField"
            desc.label    = "Insert new field..."
            desc.owner    = self
            idaapi.register_action(desc)
            '''
            print("Hex-rays %s has been detected, %s is ready to use" % (idaapi.get_hexrays_version(), self.wanted_name))
            return idaapi.PLUGIN_KEEP
        
    
    def run(self, arg):
        view    = idaapi.get_current_viewer()
        psuedo  = idaapi.get_widget_vdui(view)
        
        # Continue if we are currently in a psuedocode window
        if psuedo:
            # Grab the current highlighted item in the window
            psuedo.get_current_item(idaapi.USE_KEYBOARD)
            
            # TODO: Local variable handling I guess?
            '''
            # Get our local variable
            local   = psuedo.item.get_lvar()
            if local:
                print("local var = %s" % local.name)
                
                # Get our existing struct
                if not local.tif.is_ptr():
                    print("datatype = %s" % local.tif)
                    sptr    = idaapi.get_struc(idaapi.get_struc_id("%s" % local.tif))
                else:
                    print("datatype = %s" % local.tif.get_ptrarr_object())
                    sptr    = idaapi.get_struc(idaapi.get_struc_id("%s" % local.tif.get_ptrarr_object()))
            '''
            
            # Get our existing struct member
            mptr    = psuedo.item.get_memptr()
            if mptr:
                
                #print (mptr)
                fname = idaapi.get_member_fullname(mptr.id)
                #print(fname)
                
                sname = fname.split('.')[0]
                sptr  = idaapi.get_struc(idaapi.get_struc_id(sname))
                
                if sptr:
                    sname = idaapi.get_struc_name(sptr.id, -1)
                    #print('structure = %s' % sname)
                    
                    # Ask the user for the member declaration
                    decl = idaapi.ask_str("", 0, "Enter member declaration (e.g., 'float the_member;')")
                    if decl:
                        # Parse input declaration
                        tinfo = idaapi.tinfo_t()
                        idaapi.parse_decl(tinfo, idaapi.cvar.idati, decl, idaapi.PT_TYP)
                        tsize = tinfo.get_size()
                        
                        match = re.match(r"\s*(\w+[\w\s\*]*?)\s+(\w+(?:\s*\[\s*\d*\s*\])*)\s*;", decl)
                        if match:
                            tname = match.group(1)
                            varname = match.group(2)
                            print('Parsed type: %s, variable name: %s' % (tname, varname))
                        else:
                            print("Failed to parse the declaration.")
                            tname = varname = None

                        print('rawdecl = %s' % decl)
                                                
                        line = idaapi.tag_remove(idaapi.get_custom_viewer_curline(view, None))
                        match = re.search(r"\[(\d+)\]", line)
                        if match:
                            psize = int(match.group(1))
                        else:
                            psize = 0
                        
                        # Mind the gap
                        gname = idaapi.get_member_name(mptr.id)
                        gsize = mptr.get_size() - tsize - psize
                        moff  = mptr.soff
                        
                        #if "gap" in gname:
                        #    gname = "gap%x" % (psize + tsize)
                        
                        # Delete existing member
                        idaapi.del_struc_member(sptr, moff)
                        
                        # Add a pre gap member if needed
                        #if psize > 0:
                        #    idaapi.add_struc_member(sptr, "gap0", moff, idaapi.FF_BYTE, None, psize)
                            
                        flags = idaapi.FF_DATA  # Basic flag for a data member
                        if tinfo.is_ptr():
                            flags |= off_flag()|idaapi.FF_DATA|idaapi.FF_DWORD  # Add the pointer flag if it's a pointer type
                            print('pointer')

                        # Create the new member
                        idaapi.add_struc_member(sptr, varname, moff + psize, flags, None, tsize)
                        
                        # Add a post gap member if needed
                        if gsize > 0:
                            idaapi.add_struc_member(sptr, gname, moff + psize + tsize, idaapi.FF_BYTE, None, gsize)
                        
                        # Refresh the psuedocode window
                        psuedo.refresh_view(view)
                    
    def term(self):
        pass
    

def PLUGIN_ENTRY():
    return lemonyfresh_t()


repository: zhouzhicun/ida-scripts
file: python/IDA/IDAScripts/plugin_zzTemplate.py
description: ida相关脚本，包括字符串解密，BR/BLR混淆, 常见加密算法实现。
created_at: 2024-09-10 08:31:38+00:00
pushed_at: 2024-09-18 03:02:56+00:00
url:
https://github.com/zhouzhicun/ida-scripts/blob/fa533f6a4130921a0013133a9777847842d87c63/python/IDA/IDAScripts/plugin_zzTemplate.py

content:
'''
插件模板：
用途：通过简单的封装，实现插件的基本逻辑.

如何使用：
1.复制下面所有代码到新的插件脚本文件中；
2.搜索 'ZZPluginTemplate' 并 全局替换为自己的插件名；
3.修改插件配置；
4.实现插件逻辑。

'''



import idaapi
from ida_idaapi import plugin_t
import zzPluginBase.pluginUtils as pluginUtils


###################################  插件逻辑  #############################################


def func1():
    print("hello func1")

def func2():
    print("hello func2")

def func3():
    print("hello func3")



#################################### 插件配置 ##################################################


ZZPluginTemplate_wanted_name = 'ZZPluginTemplate'
ZZPluginTemplate_comment = ''
ZZPluginTemplate_help = ''
ZZPluginTemplate_wanted_hotkey = ''
ZZPluginTemplate_flags = idaapi.PLUGIN_KEEP

ZZPluginTemplateMenuConfig = pluginUtils.PluginMenuConfig("ZZPluginTemplate/", [
    pluginUtils.PluginSubMenu('my:func1', 'func1', func1),
    pluginUtils.PluginSubMenu('my:func2', 'func2', func2),
    pluginUtils.PluginSubMenu('my:func3', 'func3', func3)
])


#################################### 插件框架 ##################################################

class ZZPluginTemplateUIHooks(idaapi.UI_Hooks):
    def finish_populating_widget_popup(self, widget, popup_handle, ctx):
        pluginUtils.registerUIHook(widget, popup_handle, ZZPluginTemplateMenuConfig)


class ZZPluginTemplate(plugin_t):

    wanted_name = ZZPluginTemplate_wanted_name
    comment = ZZPluginTemplate_comment
    help = ZZPluginTemplate_help
    wanted_hotkey = ZZPluginTemplate_wanted_hotkey
    flags = ZZPluginTemplate_flags


    def init(self):

        pluginUtils.registerAction(ZZPluginTemplateMenuConfig)

        global my_ui_hooks
        my_ui_hooks = ZZPluginTemplateUIHooks()
        my_ui_hooks.hook()

        return idaapi.PLUGIN_KEEP

    # 插件运行中 这里是主要逻辑
    def run(self, arg: int):
        pass

    def term(self):
        pass 


# 注册插件
def PLUGIN_ENTRY():
    return ZZPluginTemplate()


repository: HACK-THE-WORLD/IDAPluginList
file: patching/plugins/patching.py
description: IDA插件集合，含项目名称及项目地址，每日定时Clone项目。
wanted_name: Patching
comment: A plugin to enable binary patching in IDA
created_at: 2022-03-21 02:05:27+00:00
pushed_at: 2024-09-21 02:08:36+00:00
url:
https://github.com/HACK-THE-WORLD/IDAPluginList/blob/a6367e2126fca45260d1052b0c54bd332490fd60/patching/plugins/patching.py

content:
#------------------------------------------------------------------------------
# Plugin Preflight
#------------------------------------------------------------------------------
#
#    the purpose of this 'preflight' is to test if the plugin is compatible
#    with the environment it is being loaded in. specifically, these preflight
#    checks are designed to be compatible with IDA 7.0+ and Python 2/3
#
#    if the environment does not meet the specifications required by the
#    plugin, this file will gracefully decline to load the plugin without
#    throwing noisy errors (besides a simple print to the IDA console)
#
#    this makes it easy to install the plugin on machines with numerous
#    versions of IDA / Python / virtualenvs which employ a shared plugin
#    directory such as the 'preferred' IDAUSR plugin directory...
#

import sys

# this plugin requires Python 3
SUPPORTED_PYTHON = sys.version_info[0] == 3

# this plugin requires IDA 7.6 or newer
try:
    import ida_pro
    import ida_idaapi
    IDA_GLOBAL_SCOPE = sys.modules['__main__']
    SUPPORTED_IDA = ida_pro.IDA_SDK_VERSION >= 760
except:
    SUPPORTED_IDA = False

# is this deemed to be a compatible environment for the plugin to load?
SUPPORTED_ENVIRONMENT = bool(SUPPORTED_IDA and SUPPORTED_PYTHON)
if not SUPPORTED_ENVIRONMENT:
    print("Patching plugin is not compatible with this IDA/Python version")

#------------------------------------------------------------------------------
# IDA Plugin Stub
#------------------------------------------------------------------------------

if SUPPORTED_ENVIRONMENT:
    import patching
    from patching.util.python import reload_package

def PLUGIN_ENTRY():
    """
    Required plugin entry point for IDAPython plugins.
    """
    return PatchingPlugin()

class PatchingPlugin(ida_idaapi.plugin_t):
    """
    The IDA Patching plugin stub.
    """

    #
    # Plugin flags:
    # - PLUGIN_PROC: Load / unload this plugin when an IDB opens / closes
    # - PLUGIN_HIDE: Hide this plugin from the IDA plugin menu
    # - PLUGIN_UNL:  Unload the plugin after calling run()
    #

    flags = ida_idaapi.PLUGIN_PROC | ida_idaapi.PLUGIN_HIDE | ida_idaapi.PLUGIN_UNL
    comment = "A plugin to enable binary patching in IDA"
    help = ""
    wanted_name = "Patching"
    wanted_hotkey = ""

    def __init__(self):
        self.__updated = getattr(IDA_GLOBAL_SCOPE, 'RESTART_REQUIRED', False)

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

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """
        if not SUPPORTED_ENVIRONMENT or self.__updated:
            return ida_idaapi.PLUGIN_SKIP

        # load the plugin core
        self.core = patching.PatchingCore(defer_load=True)

        # inject a reference to the plugin context into the IDA console scope
        IDA_GLOBAL_SCOPE.patching = self

        # mark the plugin as loaded
        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        pass

    def term(self):
        """
        This is called by IDA when it is unloading the plugin.
        """
        try:
            self.core.unload()
        except Exception as e:
            pass
        self.core = None

    #--------------------------------------------------------------------------
    # Development Helpers
    #--------------------------------------------------------------------------

    def reload(self):
        """
        Hot-reload the plugin.
        """
        if self.core:
            self.core.unload()
        reload_package(patching)
        self.core = patching.PatchingCore()


repository: vAlerainTech/vAlerain-Ark
file: cmake-build-debug/IDA_Pro_7.7/plugins/patching.py
wanted_name: Patching
comment: A plugin to enable binary patching in IDA
created_at: 2024-03-25 14:26:36+00:00
pushed_at: 2024-07-27 05:07:46+00:00
url:
https://github.com/vAlerainTech/vAlerain-Ark/blob/df8bcafeda091d30ceade7bb2c7c73b5ae351a38/cmake-build-debug/IDA_Pro_7.7/plugins/patching.py

content:
#------------------------------------------------------------------------------
# Plugin Preflight
#------------------------------------------------------------------------------
#
#    the purpose of this 'preflight' is to test if the plugin is compatible
#    with the environment it is being loaded in. specifically, these preflight
#    checks are designed to be compatible with IDA 7.0+ and Python 2/3
#
#    if the environment does not meet the specifications required by the
#    plugin, this file will gracefully decline to load the plugin without
#    throwing noisy errors (besides a simple print to the IDA console)
#
#    this makes it easy to install the plugin on machines with numerous
#    versions of IDA / Python / virtualenvs which employ a shared plugin
#    directory such as the 'preferred' IDAUSR plugin directory...
#

import sys

# this plugin requires Python 3
SUPPORTED_PYTHON = sys.version_info[0] == 3

# this plugin requires IDA 7.6 or newer
try:
    import ida_pro
    import ida_idaapi
    IDA_GLOBAL_SCOPE = sys.modules['__main__']
    SUPPORTED_IDA = ida_pro.IDA_SDK_VERSION >= 760
except:
    SUPPORTED_IDA = False

# is this deemed to be a compatible environment for the plugin to load?
SUPPORTED_ENVIRONMENT = bool(SUPPORTED_IDA and SUPPORTED_PYTHON)
if not SUPPORTED_ENVIRONMENT:
    print("Patching plugin is not compatible with this IDA/Python version")

#------------------------------------------------------------------------------
# IDA Plugin Stub
#------------------------------------------------------------------------------

if SUPPORTED_ENVIRONMENT:
    import patching
    from patching.util.python import reload_package

def PLUGIN_ENTRY():
    """
    Required plugin entry point for IDAPython plugins.
    """
    return PatchingPlugin()

class PatchingPlugin(ida_idaapi.plugin_t):
    """
    The IDA Patching plugin stub.
    """

    #
    # Plugin flags:
    # - PLUGIN_PROC: Load / unload this plugin when an IDB opens / closes
    # - PLUGIN_HIDE: Hide this plugin from the IDA plugin menu
    # - PLUGIN_UNL:  Unload the plugin after calling run()
    #

    flags = ida_idaapi.PLUGIN_PROC | ida_idaapi.PLUGIN_HIDE | ida_idaapi.PLUGIN_UNL
    comment = "A plugin to enable binary patching in IDA"
    help = ""
    wanted_name = "Patching"
    wanted_hotkey = ""

    def __init__(self):
        self.__updated = getattr(IDA_GLOBAL_SCOPE, 'RESTART_REQUIRED', False)

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

    def init(self):
        """
        This is called by IDA when it is loading the plugin.
        """
        if not SUPPORTED_ENVIRONMENT or self.__updated:
            return ida_idaapi.PLUGIN_SKIP

        # load the plugin core
        self.core = patching.PatchingCore(defer_load=True)

        # inject a reference to the plugin context into the IDA console scope
        IDA_GLOBAL_SCOPE.patching = self

        # mark the plugin as loaded
        return ida_idaapi.PLUGIN_KEEP

    def run(self, arg):
        """
        This is called by IDA when this file is loaded as a script.
        """
        pass

    def term(self):
        """
        This is called by IDA when it is unloading the plugin.
        """
        try:
            self.core.unload()
        except Exception as e:
            pass
        self.core = None

    #--------------------------------------------------------------------------
    # Development Helpers
    #--------------------------------------------------------------------------

    def reload(self):
        """
        Hot-reload the plugin.
        """
        if self.core:
            self.core.unload()
        reload_package(patching)
        self.core = patching.PatchingCore()


repository: BraveCattle/ctf
file: tools/ida_pro/IDA_Pro_7.7/plugins/patching.py
wanted_name: Patching
comment: A plugin to enable binary patching in IDA
created_at: 2024-01-23 08:17:51+00:00
pushed_at: 2024-10-23 09:31:30+00:00
url:
https://github.com/BraveCattle/ctf/blob/5858d0da5c048eb8468ff8be1d63fbc50d61b601/tools/ida_pro/IDA_Pro_7.7/plugins/patching.py


repository: carsond135/malwareanalysis
file: tools/IDA_Pro_7.7/IDA_Pro_7.7/plugins/patching.py
description: NKU恶意代码分析
wanted_name: Patching
comment: A plugin to enable binary patching in IDA
created_at: 2023-06-25 19:16:29+00:00
pushed_at: 2022-12-18 09:34:14+00:00
url:
https://github.com/carsond135/malwareanalysis/blob/45c8bf862a54239ff1e7c91313eccf9cf7d4a35b/tools/IDA_Pro_7.7/IDA_Pro_7.7/plugins/patching.py


repository: jevinskie/ghidra-ublaze
file: GhidraBuild/IDAPro/Python/7xx/plugins/xml_exporter.py
description: Ghidra MicroBlaze architecture
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2022-02-23 16:31:29+00:00
pushed_at: 2022-03-06 22:21:15+00:00
url:
https://github.com/jevinskie/ghidra-ublaze/blob/b2d4c97a9a6e6c1298b31e6195373e70c94137d2/GhidraBuild/IDAPro/Python/7xx/plugins/xml_exporter.py

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

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


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


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


    def run(self, arg):
        """
        run function for XML Exporter plugin.
        
        Args:
            arg: Integer, non-zero value enables auto-run feature for
                IDA batch (no gui) processing mode. Default is 0.
        """
        st = idc.set_ida_state(idc.IDA_STATUS_WORK)
        xml = idaxml.XmlExporter(arg)
        try:
            try:
                xml.export_xml()
            except idaxml.Cancelled:
                ida_kernwin.hide_wait_box()
                msg = "XML Export cancelled!"
                print "\n" + msg
                idc.warning(msg)
            except:
                ida_kernwin.hide_wait_box()
                msg = "***** Exception occurred: XML Exporter failed! *****"
                print "\n" + msg + "\n", sys.exc_type, sys.exc_value
                idc.warning(msg)
        finally:
            xml.cleanup()
            ida_auto.set_ida_state(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlExporterPlugin()


repository: ImpaleAskewArdently/ghidra_hex_bin
file: GhidraBuild/IDAPro/Python/7xx/plugins/xml_exporter.py
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2024-08-01 03:15:40+00:00
pushed_at: 2024-08-01 04:30:29+00:00
url:
https://github.com/ImpaleAskewArdently/ghidra_hex_bin/blob/06ca3190d1d9519df8513489ce4f539f7be2af03/GhidraBuild/IDAPro/Python/7xx/plugins/xml_exporter.py

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

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


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


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


    def run(self, arg):
        """
        run function for XML Exporter plugin.
        
        Args:
            arg: Integer, non-zero value enables auto-run feature for
                IDA batch (no gui) processing mode. Default is 0.
        """
        st = idc.set_ida_state(idc.IDA_STATUS_WORK)
        xml = idaxml.XmlExporter(arg)
        try:
            try:
                xml.export_xml()
            except idaxml.Cancelled:
                ida_kernwin.hide_wait_box()
                msg = "XML Export cancelled!"
                print "\n" + msg
                idc.warning(msg)
            except:
                ida_kernwin.hide_wait_box()
                msg = "***** Exception occurred: XML Exporter failed! *****"
                print "\n" + msg + "\n", sys.exc_type, sys.exc_value
                idc.warning(msg)
        finally:
            xml.cleanup()
            ida_auto.set_ida_state(st)


    def term(self):
        pass


def PLUGIN_ENTRY():
    return XmlExporterPlugin()


repository: OSUSecLab/PaymentScope
file: Tools/ghidra_10.0.3_PUBLIC/Extensions/IDAPro/Python/7xx/plugins/xml_exporter.py
wanted_name: XML Exporter
comment: Export database as XML file
created_at: 2022-08-12 01:57:01+00:00
pushed_at: 2022-08-12 02:19:05+00:00
url:
https://github.com/OSUSecLab/PaymentScope/blob/ef8f29c79834c0a6e5d08e8420bd9d04b1a78cbe/Tools/ghidra_10.0.3_PUBLIC/Extensions/IDAPro/Python/7xx/plugins/xml_exporter.py


repository: m223744/MalwareAnalysisCapstone
file: mainCode/ghidraApi/ghidra/Extensions/IDAPro/Python/7xx/plugins/xml_exporter.py
description: Automated Static Malware Analysis Capstone
wanted_name: XML Exporter
comment: Export database as XML 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/7xx/plugins/xml_exporter.py


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

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

import	idaapi
import  idc
import	struct
import  colorsys

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


repository: malefs/security-smell-detector-python-gist
file: gist-src/b59679e31dbdb01064d6bceb827ed88b.py
wanted_name: HexJump
comment: Easily follow DWORD addresses within hex dump
created_at: 2019-11-23 19:14:08+00:00
pushed_at: 2020-07-26 03:38:04+00:00
url:
https://github.com/malefs/security-smell-detector-python-gist/blob/e90764deb06ae4d3c45e702db7ad00351520348f/gist-src/b59679e31dbdb01064d6bceb827ed88b.py

content:
import idaapi
import idc


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

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


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


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

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

    def run(self, arg):
        pass

    def term(self):
        pass


def PLUGIN_ENTRY():
    return HexJump()


repository: malefs/security-smell-detector-python-gist
file: gist-hash/b59679e31dbdb01064d6bceb827ed88b/snippet.py
wanted_name: HexJump
comment: Easily follow DWORD addresses within hex dump
created_at: 2019-11-23 19:14:08+00:00
pushed_at: 2020-07-26 03:38:04+00:00
url:
https://github.com/malefs/security-smell-detector-python-gist/blob/e90764deb06ae4d3c45e702db7ad00351520348f/gist-hash/b59679e31dbdb01064d6bceb827ed88b/snippet.py

content:
import idaapi
import idc


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

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


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


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

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

    def run(self, arg):
        pass

    def term(self):
        pass


def PLUGIN_ENTRY():
    return HexJump()


repository: PyEGo/exp-gist
file: hard-gists-me/b59679e31dbdb01064d6bceb827ed88b/snippet.py
description: Experiment results on hard-gists dataset
wanted_name: HexJump
comment: Easily follow DWORD addresses within hex dump
created_at: 2021-04-18 11:16:06+00:00
pushed_at: 2022-11-18 17:22:11+00:00
url:
https://github.com/PyEGo/exp-gist/blob/bb8212e435ab525bb8ebdee2f620f850133a9b99/hard-gists-me/b59679e31dbdb01064d6bceb827ed88b/snippet.py


repository: PyEGo/exp-gist
file: hard-gists-ego/b59679e31dbdb01064d6bceb827ed88b/snippet.py
description: Experiment results on hard-gists dataset
wanted_name: HexJump
comment: Easily follow DWORD addresses within hex dump
created_at: 2021-04-18 11:16:06+00:00
pushed_at: 2022-11-18 17:22:11+00:00
url:
https://github.com/PyEGo/exp-gist/blob/bb8212e435ab525bb8ebdee2f620f850133a9b99/hard-gists-ego/b59679e31dbdb01064d6bceb827ed88b/snippet.py


repository: vAlerainTech/vAlerain-Ark
file: cmake-build-debug/IDA_Pro_7.7/plugins/SyncPlugin.py
wanted_name: ret-sync
comment: Reverse-Engineering Tools synchronization, ret-sync .
created_at: 2024-03-25 14:26:36+00:00
pushed_at: 2024-07-27 05:07:46+00:00
url:
https://github.com/vAlerainTech/vAlerain-Ark/blob/df8bcafeda091d30ceade7bb2c7c73b5ae351a38/cmake-build-debug/IDA_Pro_7.7/plugins/SyncPlugin.py

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

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

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

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

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

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

from idaapi import PluginForm


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

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

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

COL_CBTRACE = rsconfig.COL_CBTRACE


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


class RequestHandler(object):

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

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

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

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

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

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

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

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

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

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

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

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

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

            offset = self.rebase_local(offset)

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

        return offset

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

        return offset

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

        return offset

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

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

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

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

        idaapi.append_cmt(ea, cmt, rptble)

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

        if self.color:
            self.cb_color(ea)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        req_ = self.normalize(req, 6)

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

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

        req_handler = self.req_handlers[rtype]

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

        idaapi.refresh_idaview_anyway()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        rs_log('export done')

    # send a translate command (Alt-F2) to the debugger (via the broker and dispatcher)
    def translate_notice(self):
        if not self.dbg_dialect:
            rs_log("idb isn't synced yet, can't translate")
            return

        ea = idaapi.get_screen_ea()
        mod = self.name.split('.')[0].strip()
        cmd = self.dbg_dialect['prefix'] + "translate 0x%x 0x%x %s" % (self.base, ea, mod)
        self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
        rs_debug("translate address 0x%x" % ea)

    # send a command to the debugger (via the broker and dispatcher)
    def cmd_notice(self, cmd, descr):
        if cmd in self.dbg_dialect:
            self.notice_broker("cmd", "\"cmd\":\"%s\"" % self.dbg_dialect[cmd])
            self.notice_anti_flood()
        else:
            rs_log("the \"%s\" command is not available for the current debugger" % cmd)

    # send a go command (Alt-F5) to the debugger (via the broker and dispatcher)
    def go_notice(self):
        self.cmd_notice('go', descr='go')

    # send a go command (Ctrl-Alt-F5) to the debugger (via the broker and dispatcher)
    def run_notice(self):
        self.cmd_notice('run', descr='run')

    # send a single trace command (F11) to the debugger (via the broker and dispatcher)
    def si_notice(self):
        self.cmd_notice('si', descr='trace')

    # send a single step command (F10) to the debugger (via the broker and dispatcher)
    def so_notice(self):
        self.cmd_notice('so', descr='step')

    # send a notice message to the broker process
    def notice_broker(self, type, args=None):
        if not self.broker_sock:
            return

        if args:
            notice = "[notice]{\"type\":\"%s\",%s}\n" % (type, args)
        else:
            notice = "[notice]{\"type\":\"%s\"}\n" % (type)

        try:
            self.broker_sock.sendall(rs_encode(notice))
        except socket.error:
            None

    def stop(self):
        if self.broker_sock:
            self.broker_sock.close()
            self.broker_sock = None

        self.cb_restore_last_line()
        idaapi.refresh_idaview_anyway()
        self.is_active = False
        rs_log("idb is disabled")

    def __init__(self, parser):
        self.color = False
        self.prev_loc = None
        self.prev_node = None
        self.name = idaapi.get_root_filename()
        self.base = idaapi.get_imagebase()
        rs_log("module base 0x%x" % self.base)
        self.base_remote = None
        self.gm = GraphManager()
        self.hexsync = Syncrays()
        self.parser = parser
        self.broker_sock = None
        self.is_active = False
        self.dbg_dialect = None
        self.req_handlers = {
            'broker': self.req_broker,
            'loc': self.req_loc,
            'cmd': self.req_cmd,
            'cmt': self.req_cmt,
            'rcmt': self.req_rcmt,
            'fcmt': self.req_fcmt,
            'raddr': self.req_raddr,
            'rbase': self.req_rbase,
            'cursor': self.req_cursor,
            'patch': self.req_patch,
            'rln': self.req_rln,
            'rrln': self.req_rrln,
            'lbl': self.req_lbl,
            'bc': self.req_bc,
            'bps_get': self.req_bps_get,
            'bps_set': self.req_bps_set,
            'modcheck': self.req_modcheck,
            'dialect': self.req_set_dbg_dialect
        }
        self.prev_req = ""  # used as a cache if json is not completely received


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


class Broker(QtCore.QProcess):

    QP_STATES = ('Not running', 'Starting', 'Running')
    QP_ERRORS = ('Failed to start', 'Crashed', 'Timedout',
                 'Read error', 'Write Error', 'Unknown Error')

    def cb_on_error(self, error):
        rs_log("[-] broker error: %s" % Broker.QP_ERRORS[error])

    def cb_broker_on_state_change(self, new_state):
        rs_debug("broker new state: %s" % Broker.QP_STATES[new_state])
        if Broker.QP_STATES[new_state] == 'Not running':
            if rsconfig.LOG_TO_FILE_ENABLE:
                rs_log('    check tmp file retsync.<broker|dispatcher>.err if you think this is an error')

    def cb_broker_on_out(self):
        # readAllStandardOutput() returns QByteArray
        data = rs_decode(self.readAllStandardOutput().data())
        batch = data.split('\n')
        for req in batch:
            self.worker.parse_exec(req.strip())

    def __init__(self, parser):
        QtCore.QProcess.__init__(self)

        self.error.connect(self.cb_on_error)
        self.readyReadStandardOutput.connect(self.cb_broker_on_out)
        self.stateChanged.connect(self.cb_broker_on_state_change)

        # create a request handler
        self.worker = RequestHandler(parser)

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


class DbgDirHlpr(object):

    @staticmethod
    def read_rsds_guid():
        guid = None
        penode = idaapi.netnode()
        penode.create(idautils.peutils_t.PE_NODE)
        rsds = penode.getblob(0, "s")

        if rsds and rsds.startswith(b'RSDS'):
            guid = ("%s" % uuid.UUID(bytes_le=rsds[4:20])).upper()

        return guid

    @staticmethod
    def read_rsds_pdb():
        penode = idaapi.netnode()
        PE_SUPSTR_PDBNM = idautils.peutils_t.PE_ALT_DBG_FPOS - 8
        penode.create(idautils.peutils_t.PE_NODE)
        pdbname = penode.supstr(PE_SUPSTR_PDBNM, 'S')
        return pdbname

    @staticmethod
    def parse_itoldyouso_output(res):
        for line in res.splitlines(True):
            line = line.strip()
            if line.startswith('pdb sig: '):
                return (line.split(':')[-1]).strip()
        return None


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


class GraphManager():

    def __init__(self):
        self.prev_node = None
        self.graph_viewer = ida_kernwin.get_current_viewer()

    def center(self):
        curnode = ida_graph.viewer_get_curnode(self.graph_viewer)

        if not (self.prev_node == curnode):
            ida_graph.viewer_center_on(self.graph_viewer, curnode)
            self.prev_node = curnode

        return curnode


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


class CheckBoxActionHandler(idaapi.action_handler_t):
    def __init__(self, cb):
        idaapi.action_handler_t.__init__(self)
        self.cb = cb

    def activate(self, ctx):
        self.cb.toggle()
        return 1

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


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


class CmdHook(ida_kernwin.UI_Hooks):

    def __init__(self):
        idaapi.UI_Hooks.__init__(self)
        self.hooked = {}
        self.bugfixed = False

        # 74sp1 BUGFIX: IDAPython: ida_kernwin.UI_Hooks.preprocess_action()
        # wouldn't allow inhibiting the action
        pattern = re.compile('preprocess_action\(self, name\) -> int')
        if pattern.search(ida_kernwin.UI_Hooks.preprocess_action.__doc__):
            self.bugfixed = True

    def minver74sp1(self):
        # idaapi.IDA_SDK_VERSION >= 740:
        return self.bugfixed

    def add_hook(self, action_name, callback):
        self.hooked[action_name] = callback

    def del_hook(self, action_name):
        del self.hooked[action_name]

    def preprocess_action(self, action_name):
        if action_name not in self.hooked:
            return 0

        self.hooked[action_name]()
        return 1


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


class SyncForm_t(PluginForm):

    hotkeys_ctx = []
    cmd_hooks = CmdHook()

    def cb_broker_started(self):
        rs_log("broker started")
        self.btn.setText("Restart")

    def cb_broker_finished(self):
        rs_log("broker finished")
        self.uninit_hotkeys()
        if self.broker:
            self.broker.worker.stop()
            self.cb_sync.stateChanged.disconnect(self.cb_change_state)
            self.cb_sync.toggle()
            self.cb_sync.stateChanged.connect(self.cb_change_state)

        self.btn.setText("Start")

    # send a kill notice to the broker
    # wait at most 2sec for him to gently kill itself
    def smooth_kill(self):
        self.uninit_hotkeys()
        if self.broker:
            broker = self.broker
            self.broker = None
            broker.worker.cb_restore_last_line()
            broker.worker.kill_notice()
            broker.waitForFinished(1500)

    def init_broker(self):
        rs_debug("init_broker")
        modname = self.input.text()
        if modname == "":
            modname = self.handle_name_aliasing()
            self.input.setText(modname)

        cmdline = "\"%s\" -u \"%s\" --idb \"%s\"" % (
                  PYTHON_PATH,
                  BROKER_PATH,
                  modname)
        rs_log("cmdline: %s" % cmdline)

        try:
            self.broker = Broker(self.parser)
            self.broker.started.connect(self.cb_broker_started)
            self.broker.finished.connect(self.cb_broker_finished)
            self.broker.start(cmdline)
        except Exception as e:
            rs_log("[-] failed to start broker: %s\n%s" % (str(e), traceback.format_exc()))
            return

        self.broker.worker.name = modname

    def init_hotkeys(self):
        hotkeys_info = (
            ('F2', self.broker.worker.bp_notice, 'BreakpointToggle'),
            ('F3', self.broker.worker.bp_oneshot_notice),
            ('F10', self.broker.worker.so_notice),
            ('F11', self.broker.worker.si_notice, 'FullScreen'),
            ('Ctrl-F1', self.broker.worker.export_bp_notice, 'ExternalHelp'),
            ('Ctrl-F2', self.broker.worker.hbp_notice),
            ('Ctrl-F3', self.broker.worker.hbp_oneshot_notice),
            ('Alt-F2', self.broker.worker.translate_notice, 'ManualInstruction'),
            ('Alt-F5', self.broker.worker.go_notice),
            ('Ctrl-Alt-F5', self.broker.worker.run_notice),
        )

        if not self.hotkeys_ctx:
            for hk_info in hotkeys_info:
                self.init_single_hotkey(*hk_info)

        # enable ida_kernwin.UI_Hooks
        if self.cmd_hooks.minver74sp1():
            self.cmd_hooks.hook()

    def init_single_hotkey(self, key, fnCb, conflict=None):
        if conflict:
            if self.cmd_hooks.minver74sp1():
                # 'hook' existing action shortcut when possible
                self.cmd_hooks.add_hook(conflict, fnCb)
                return
            else:
                # 'mute' existing action shortcut
                ida_kernwin.update_action_shortcut(conflict, None)

        ctx = idaapi.add_hotkey(key, fnCb)
        if ctx is None:
            rs_log("failed to register hotkey %s" % key)
            del ctx
        else:
            self.hotkeys_ctx.append((ctx, key, conflict))

    def uninit_hotkeys(self):
        # disable ida_kernwin.UI_Hooks
        if self.cmd_hooks.minver74sp1():
            self.cmd_hooks.unhook()

        if not self.hotkeys_ctx:
            return

        # delete registered context and restore original action
        for ctx, key, conflict in self.hotkeys_ctx:
            if idaapi.del_hotkey(ctx):
                del ctx
            else:
                rs_log("failed to delete hotkey %s" % key)

            if conflict and not self.cmd_hooks.minver74sp1():
                ida_kernwin.update_action_shortcut(conflict, key)

        self.hotkeys_ctx = []

    def cb_btn_restart(self):
        rs_log('restarting broker')
        if self.cb_sync.checkState() == QtCore.Qt.Checked:
            self.cb_sync.toggle()
            time.sleep(0.1)
        self.cb_sync.toggle()

    def cb_change_state(self, state):
        if state == QtCore.Qt.Checked:
            rs_log("sync enabled")
            # Restart broker
            self.hotkeys_ctx = []
            self.init_broker()
        else:
            if self.broker:
                self.smooth_kill()
            rs_log("sync disabled\n")

    def cb_hexrays_sync_state(self, state):
        if self.broker:
            if state == QtCore.Qt.Checked:
                rs_log("hexrays sync enabled\n")
                self.broker.worker.hexsync.enable()
            else:
                rs_log("hexrays sync disabled\n")
                self.broker.worker.hexsync.disable()

    def cb_hexrays_toggle(self):
        self.cb_hexrays.toggle()

    # issue a warning if pdb name is different from
    # the name used to register the idb to the dispatcher
    def pdb_name_warning(self, name):
        pdbpath = DbgDirHlpr.read_rsds_pdb()
        if not pdbpath:
            return

        normpath = os.path.normpath(pdbpath.replace("\\", "\\\\"))
        pdb_root, pdb_ext = os.path.splitext(os.path.basename(normpath))
        mod_root, mod_ext = os.path.splitext(name)

        if pdb_root.strip() != mod_root.strip():
            rs_log("hint: pdb name ('%s') differs from registered module name ('%s')" % (pdb_root+mod_ext, name))

    # discover the name used to expose the idb, default is from get_root_filename
    # alias can be defined in '.sync' configuration file
    def handle_name_aliasing(self):
        name = idaapi.get_root_filename()
        rs_log("default idb name: %s" % name)

        try:
            conf = load_configuration(name)
            if conf.path:
                rs_log("found config file: %s" % repr(conf))
            if conf.alias:
                name = conf.alias
                rs_log("overwrite idb name with %s" % name)
        except Exception as e:
            rs_log('failed to load configuration file')

        self.pdb_name_warning(name)
        return name

    def OnCreate(self, form):
        rs_debug("form create")

        # get parent widget
        parent = self.FormToPyQtWidget(form)

        # create global sync checkbox
        self.cb_sync = QtWidgets.QCheckBox('Synchronization enable')
        self.cb_sync.move(20, 20)
        self.cb_sync.stateChanged.connect(self.cb_change_state)

        # create hexrays sync checkbox
        self.cb_hexrays = QtWidgets.QCheckBox('Hex-Rays Synchronization enable')
        self.cb_hexrays.move(20, 20)
        self.cb_hexrays.stateChanged.connect(self.cb_hexrays_sync_state)

        # create label
        label = QtWidgets.QLabel('Overwrite idb name:')
        name = self.handle_name_aliasing()

        # create input field
        self.input = QtWidgets.QLineEdit(parent)
        self.input.setText(name)
        self.input.setMaxLength = 256
        self.input.setFixedWidth(300)

        # create restart button
        self.btn = QtWidgets.QPushButton('restart', parent)
        self.btn.setToolTip('Restart broker.')
        self.btn.clicked.connect(self.cb_btn_restart)

        # create layout
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.cb_sync)
        layout.addWidget(self.cb_hexrays)
        layout.addWidget(label)
        layout.addWidget(self.input)
        layout.addWidget(self.btn, 2, 2)
        layout.setColumnStretch(4, 1)
        layout.setRowStretch(4, 1)
        parent.setLayout(layout)

        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-a', '--address', nargs=1, action='store')
        self.parser.add_argument('msg', nargs=argparse.REMAINDER)

        # synchronization is enabled by default
        self.cb_sync.toggle()

        # register action for hexrays sync
        action_hex_sync_desc = idaapi.action_desc_t(
            'hexrays_sync_toogle:action',
            'Toggle Hex-Rays syncing',
            CheckBoxActionHandler(self.cb_hexrays),
            'Ctrl+H',
            'Toggle Hex-Rays syncing',
            198)

        idaapi.register_action(action_hex_sync_desc)
        idaapi.attach_action_to_toolbar(
            "DebugToolBar",
            'hexrays_sync_toogle:action')

        # register action for global sync
        action_g_sync_desc = idaapi.action_desc_t(
            'g_sync_toogle:action',
            'Toggle syncing',
            CheckBoxActionHandler(self.cb_sync),
            'Ctrl+Shift+S',
            'Toggle syncing',
            203)

        idaapi.register_action(action_g_sync_desc)
        idaapi.attach_action_to_toolbar(
            "DebugToolBar",
            'g_sync_toogle:action')

    def OnClose(self, form):
        rs_debug("form close")
        self.smooth_kill()

        idaapi.unregister_action('hexrays_sync_toogle:action')
        idaapi.detach_action_from_toolbar(
            "DebugToolBar",
            'hexrays_sync_toogle:action')

        idaapi.unregister_action('g_sync_toogle:action')
        idaapi.detach_action_from_toolbar(
            "DebugToolBar",
            'g_sync_toogle:action')

        global SyncForm
        del SyncForm
        SyncForm = None

    def Show(self):
        return PluginForm.Show(self, "ret-sync", options=PluginForm.WOPN_PERSIST)


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


class RetSyncPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC
    comment = 'Reverse-Engineering Tools synchronization, ret-sync .'
    help = 'Synchronize a debugging session with IDA.'
    wanted_name = 'ret-sync'
    wanted_hotkey = 'Alt-Shift-S'
    global SyncForm
    SyncForm = None

    def init(self):
        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    def run(self, arg):
        if not idaapi.get_root_filename():
            rs_log('please load a file/idb before')
            return

        global SyncForm
        if not SyncForm:
            SyncForm = SyncForm_t()
            SyncForm.Show()
            rs_log("plugin loaded")


def PLUGIN_ENTRY():
    return RetSyncPlugin()


if __name__ == "__main__":
    rs_log("ret-sync is an IDA Pro plugin, please see README for installation notes")


repository: BraveCattle/ctf
file: tools/ida_pro/IDA_Pro_7.7/plugins/SyncPlugin.py
wanted_name: ret-sync
comment: Reverse-Engineering Tools synchronization, ret-sync .
created_at: 2024-01-23 08:17:51+00:00
pushed_at: 2024-10-23 09:31:30+00:00
url:
https://github.com/BraveCattle/ctf/blob/5858d0da5c048eb8468ff8be1d63fbc50d61b601/tools/ida_pro/IDA_Pro_7.7/plugins/SyncPlugin.py

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

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

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

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

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

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

from idaapi import PluginForm


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

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

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

COL_CBTRACE = rsconfig.COL_CBTRACE


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


class RequestHandler(object):

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

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

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

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

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

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

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

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

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

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

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

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

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

            offset = self.rebase_local(offset)

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

        return offset

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

        return offset

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

        return offset

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

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

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

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

        idaapi.append_cmt(ea, cmt, rptble)

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

        if self.color:
            self.cb_color(ea)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        req_ = self.normalize(req, 6)

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

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

        req_handler = self.req_handlers[rtype]

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

        idaapi.refresh_idaview_anyway()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        rs_log('export done')

    # send a translate command (Alt-F2) to the debugger (via the broker and dispatcher)
    def translate_notice(self):
        if not self.dbg_dialect:
            rs_log("idb isn't synced yet, can't translate")
            return

        ea = idaapi.get_screen_ea()
        mod = self.name.split('.')[0].strip()
        cmd = self.dbg_dialect['prefix'] + "translate 0x%x 0x%x %s" % (self.base, ea, mod)
        self.notice_broker("cmd", "\"cmd\":\"%s\"" % cmd)
        rs_debug("translate address 0x%x" % ea)

    # send a command to the debugger (via the broker and dispatcher)
    def cmd_notice(self, cmd, descr):
        if cmd in self.dbg_dialect:
            self.notice_broker("cmd", "\"cmd\":\"%s\"" % self.dbg_dialect[cmd])
            self.notice_anti_flood()
        else:
            rs_log("the \"%s\" command is not available for the current debugger" % cmd)

    # send a go command (Alt-F5) to the debugger (via the broker and dispatcher)
    def go_notice(self):
        self.cmd_notice('go', descr='go')

    # send a go command (Ctrl-Alt-F5) to the debugger (via the broker and dispatcher)
    def run_notice(self):
        self.cmd_notice('run', descr='run')

    # send a single trace command (F11) to the debugger (via the broker and dispatcher)
    def si_notice(self):
        self.cmd_notice('si', descr='trace')

    # send a single step command (F10) to the debugger (via the broker and dispatcher)
    def so_notice(self):
        self.cmd_notice('so', descr='step')

    # send a notice message to the broker process
    def notice_broker(self, type, args=None):
        if not self.broker_sock:
            return

        if args:
            notice = "[notice]{\"type\":\"%s\",%s}\n" % (type, args)
        else:
            notice = "[notice]{\"type\":\"%s\"}\n" % (type)

        try:
            self.broker_sock.sendall(rs_encode(notice))
        except socket.error:
            None

    def stop(self):
        if self.broker_sock:
            self.broker_sock.close()
            self.broker_sock = None

        self.cb_restore_last_line()
        idaapi.refresh_idaview_anyway()
        self.is_active = False
        rs_log("idb is disabled")

    def __init__(self, parser):
        self.color = False
        self.prev_loc = None
        self.prev_node = None
        self.name = idaapi.get_root_filename()
        self.base = idaapi.get_imagebase()
        rs_log("module base 0x%x" % self.base)
        self.base_remote = None
        self.gm = GraphManager()
        self.hexsync = Syncrays()
        self.parser = parser
        self.broker_sock = None
        self.is_active = False
        self.dbg_dialect = None
        self.req_handlers = {
            'broker': self.req_broker,
            'loc': self.req_loc,
            'cmd': self.req_cmd,
            'cmt': self.req_cmt,
            'rcmt': self.req_rcmt,
            'fcmt': self.req_fcmt,
            'raddr': self.req_raddr,
            'rbase': self.req_rbase,
            'cursor': self.req_cursor,
            'patch': self.req_patch,
            'rln': self.req_rln,
            'rrln': self.req_rrln,
            'lbl': self.req_lbl,
            'bc': self.req_bc,
            'bps_get': self.req_bps_get,
            'bps_set': self.req_bps_set,
            'modcheck': self.req_modcheck,
            'dialect': self.req_set_dbg_dialect
        }
        self.prev_req = ""  # used as a cache if json is not completely received


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


class Broker(QtCore.QProcess):

    QP_STATES = ('Not running', 'Starting', 'Running')
    QP_ERRORS = ('Failed to start', 'Crashed', 'Timedout',
                 'Read error', 'Write Error', 'Unknown Error')

    def cb_on_error(self, error):
        rs_log("[-] broker error: %s" % Broker.QP_ERRORS[error])

    def cb_broker_on_state_change(self, new_state):
        rs_debug("broker new state: %s" % Broker.QP_STATES[new_state])
        if Broker.QP_STATES[new_state] == 'Not running':
            if rsconfig.LOG_TO_FILE_ENABLE:
                rs_log('    check tmp file retsync.<broker|dispatcher>.err if you think this is an error')

    def cb_broker_on_out(self):
        # readAllStandardOutput() returns QByteArray
        data = rs_decode(self.readAllStandardOutput().data())
        batch = data.split('\n')
        for req in batch:
            self.worker.parse_exec(req.strip())

    def __init__(self, parser):
        QtCore.QProcess.__init__(self)

        self.error.connect(self.cb_on_error)
        self.readyReadStandardOutput.connect(self.cb_broker_on_out)
        self.stateChanged.connect(self.cb_broker_on_state_change)

        # create a request handler
        self.worker = RequestHandler(parser)

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


class DbgDirHlpr(object):

    @staticmethod
    def read_rsds_guid():
        guid = None
        penode = idaapi.netnode()
        penode.create(idautils.peutils_t.PE_NODE)
        rsds = penode.getblob(0, "s")

        if rsds and rsds.startswith(b'RSDS'):
            guid = ("%s" % uuid.UUID(bytes_le=rsds[4:20])).upper()

        return guid

    @staticmethod
    def read_rsds_pdb():
        penode = idaapi.netnode()
        PE_SUPSTR_PDBNM = idautils.peutils_t.PE_ALT_DBG_FPOS - 8
        penode.create(idautils.peutils_t.PE_NODE)
        pdbname = penode.supstr(PE_SUPSTR_PDBNM, 'S')
        return pdbname

    @staticmethod
    def parse_itoldyouso_output(res):
        for line in res.splitlines(True):
            line = line.strip()
            if line.startswith('pdb sig: '):
                return (line.split(':')[-1]).strip()
        return None


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


class GraphManager():

    def __init__(self):
        self.prev_node = None
        self.graph_viewer = ida_kernwin.get_current_viewer()

    def center(self):
        curnode = ida_graph.viewer_get_curnode(self.graph_viewer)

        if not (self.prev_node == curnode):
            ida_graph.viewer_center_on(self.graph_viewer, curnode)
            self.prev_node = curnode

        return curnode


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


class CheckBoxActionHandler(idaapi.action_handler_t):
    def __init__(self, cb):
        idaapi.action_handler_t.__init__(self)
        self.cb = cb

    def activate(self, ctx):
        self.cb.toggle()
        return 1

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


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


class CmdHook(ida_kernwin.UI_Hooks):

    def __init__(self):
        idaapi.UI_Hooks.__init__(self)
        self.hooked = {}
        self.bugfixed = False

        # 74sp1 BUGFIX: IDAPython: ida_kernwin.UI_Hooks.preprocess_action()
        # wouldn't allow inhibiting the action
        pattern = re.compile('preprocess_action\(self, name\) -> int')
        if pattern.search(ida_kernwin.UI_Hooks.preprocess_action.__doc__):
            self.bugfixed = True

    def minver74sp1(self):
        # idaapi.IDA_SDK_VERSION >= 740:
        return self.bugfixed

    def add_hook(self, action_name, callback):
        self.hooked[action_name] = callback

    def del_hook(self, action_name):
        del self.hooked[action_name]

    def preprocess_action(self, action_name):
        if action_name not in self.hooked:
            return 0

        self.hooked[action_name]()
        return 1


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


class SyncForm_t(PluginForm):

    hotkeys_ctx = []
    cmd_hooks = CmdHook()

    def cb_broker_started(self):
        rs_log("broker started")
        self.btn.setText("Restart")

    def cb_broker_finished(self):
        rs_log("broker finished")
        self.uninit_hotkeys()
        if self.broker:
            self.broker.worker.stop()
            self.cb_sync.stateChanged.disconnect(self.cb_change_state)
            self.cb_sync.toggle()
            self.cb_sync.stateChanged.connect(self.cb_change_state)

        self.btn.setText("Start")

    # send a kill notice to the broker
    # wait at most 2sec for him to gently kill itself
    def smooth_kill(self):
        self.uninit_hotkeys()
        if self.broker:
            broker = self.broker
            self.broker = None
            broker.worker.cb_restore_last_line()
            broker.worker.kill_notice()
            broker.waitForFinished(1500)

    def init_broker(self):
        rs_debug("init_broker")
        modname = self.input.text()
        if modname == "":
            modname = self.handle_name_aliasing()
            self.input.setText(modname)

        cmdline = "\"%s\" -u \"%s\" --idb \"%s\"" % (
                  PYTHON_PATH,
                  BROKER_PATH,
                  modname)
        rs_log("cmdline: %s" % cmdline)

        try:
            self.broker = Broker(self.parser)
            self.broker.started.connect(self.cb_broker_started)
            self.broker.finished.connect(self.cb_broker_finished)
            self.broker.start(cmdline)
        except Exception as e:
            rs_log("[-] failed to start broker: %s\n%s" % (str(e), traceback.format_exc()))
            return

        self.broker.worker.name = modname

    def init_hotkeys(self):
        hotkeys_info = (
            ('F2', self.broker.worker.bp_notice, 'BreakpointToggle'),
            ('F3', self.broker.worker.bp_oneshot_notice),
            ('F10', self.broker.worker.so_notice),
            ('F11', self.broker.worker.si_notice, 'FullScreen'),
            ('Ctrl-F1', self.broker.worker.export_bp_notice, 'ExternalHelp'),
            ('Ctrl-F2', self.broker.worker.hbp_notice),
            ('Ctrl-F3', self.broker.worker.hbp_oneshot_notice),
            ('Alt-F2', self.broker.worker.translate_notice, 'ManualInstruction'),
            ('Alt-F5', self.broker.worker.go_notice),
            ('Ctrl-Alt-F5', self.broker.worker.run_notice),
        )

        if not self.hotkeys_ctx:
            for hk_info in hotkeys_info:
                self.init_single_hotkey(*hk_info)

        # enable ida_kernwin.UI_Hooks
        if self.cmd_hooks.minver74sp1():
            self.cmd_hooks.hook()

    def init_single_hotkey(self, key, fnCb, conflict=None):
        if conflict:
            if self.cmd_hooks.minver74sp1():
                # 'hook' existing action shortcut when possible
                self.cmd_hooks.add_hook(conflict, fnCb)
                return
            else:
                # 'mute' existing action shortcut
                ida_kernwin.update_action_shortcut(conflict, None)

        ctx = idaapi.add_hotkey(key, fnCb)
        if ctx is None:
            rs_log("failed to register hotkey %s" % key)
            del ctx
        else:
            self.hotkeys_ctx.append((ctx, key, conflict))

    def uninit_hotkeys(self):
        # disable ida_kernwin.UI_Hooks
        if self.cmd_hooks.minver74sp1():
            self.cmd_hooks.unhook()

        if not self.hotkeys_ctx:
            return

        # delete registered context and restore original action
        for ctx, key, conflict in self.hotkeys_ctx:
            if idaapi.del_hotkey(ctx):
                del ctx
            else:
                rs_log("failed to delete hotkey %s" % key)

            if conflict and not self.cmd_hooks.minver74sp1():
                ida_kernwin.update_action_shortcut(conflict, key)

        self.hotkeys_ctx = []

    def cb_btn_restart(self):
        rs_log('restarting broker')
        if self.cb_sync.checkState() == QtCore.Qt.Checked:
            self.cb_sync.toggle()
            time.sleep(0.1)
        self.cb_sync.toggle()

    def cb_change_state(self, state):
        if state == QtCore.Qt.Checked:
            rs_log("sync enabled")
            # Restart broker
            self.hotkeys_ctx = []
            self.init_broker()
        else:
            if self.broker:
                self.smooth_kill()
            rs_log("sync disabled\n")

    def cb_hexrays_sync_state(self, state):
        if self.broker:
            if state == QtCore.Qt.Checked:
                rs_log("hexrays sync enabled\n")
                self.broker.worker.hexsync.enable()
            else:
                rs_log("hexrays sync disabled\n")
                self.broker.worker.hexsync.disable()

    def cb_hexrays_toggle(self):
        self.cb_hexrays.toggle()

    # issue a warning if pdb name is different from
    # the name used to register the idb to the dispatcher
    def pdb_name_warning(self, name):
        pdbpath = DbgDirHlpr.read_rsds_pdb()
        if not pdbpath:
            return

        normpath = os.path.normpath(pdbpath.replace("\\", "\\\\"))
        pdb_root, pdb_ext = os.path.splitext(os.path.basename(normpath))
        mod_root, mod_ext = os.path.splitext(name)

        if pdb_root.strip() != mod_root.strip():
            rs_log("hint: pdb name ('%s') differs from registered module name ('%s')" % (pdb_root+mod_ext, name))

    # discover the name used to expose the idb, default is from get_root_filename
    # alias can be defined in '.sync' configuration file
    def handle_name_aliasing(self):
        name = idaapi.get_root_filename()
        rs_log("default idb name: %s" % name)

        try:
            conf = load_configuration(name)
            if conf.path:
                rs_log("found config file: %s" % repr(conf))
            if conf.alias:
                name = conf.alias
                rs_log("overwrite idb name with %s" % name)
        except Exception as e:
            rs_log('failed to load configuration file')

        self.pdb_name_warning(name)
        return name

    def OnCreate(self, form):
        rs_debug("form create")

        # get parent widget
        parent = self.FormToPyQtWidget(form)

        # create global sync checkbox
        self.cb_sync = QtWidgets.QCheckBox('Synchronization enable')
        self.cb_sync.move(20, 20)
        self.cb_sync.stateChanged.connect(self.cb_change_state)

        # create hexrays sync checkbox
        self.cb_hexrays = QtWidgets.QCheckBox('Hex-Rays Synchronization enable')
        self.cb_hexrays.move(20, 20)
        self.cb_hexrays.stateChanged.connect(self.cb_hexrays_sync_state)

        # create label
        label = QtWidgets.QLabel('Overwrite idb name:')
        name = self.handle_name_aliasing()

        # create input field
        self.input = QtWidgets.QLineEdit(parent)
        self.input.setText(name)
        self.input.setMaxLength = 256
        self.input.setFixedWidth(300)

        # create restart button
        self.btn = QtWidgets.QPushButton('restart', parent)
        self.btn.setToolTip('Restart broker.')
        self.btn.clicked.connect(self.cb_btn_restart)

        # create layout
        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.cb_sync)
        layout.addWidget(self.cb_hexrays)
        layout.addWidget(label)
        layout.addWidget(self.input)
        layout.addWidget(self.btn, 2, 2)
        layout.setColumnStretch(4, 1)
        layout.setRowStretch(4, 1)
        parent.setLayout(layout)

        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-a', '--address', nargs=1, action='store')
        self.parser.add_argument('msg', nargs=argparse.REMAINDER)

        # synchronization is enabled by default
        self.cb_sync.toggle()

        # register action for hexrays sync
        action_hex_sync_desc = idaapi.action_desc_t(
            'hexrays_sync_toogle:action',
            'Toggle Hex-Rays syncing',
            CheckBoxActionHandler(self.cb_hexrays),
            'Ctrl+H',
            'Toggle Hex-Rays syncing',
            198)

        idaapi.register_action(action_hex_sync_desc)
        idaapi.attach_action_to_toolbar(
            "DebugToolBar",
            'hexrays_sync_toogle:action')

        # register action for global sync
        action_g_sync_desc = idaapi.action_desc_t(
            'g_sync_toogle:action',
            'Toggle syncing',
            CheckBoxActionHandler(self.cb_sync),
            'Ctrl+Shift+S',
            'Toggle syncing',
            203)

        idaapi.register_action(action_g_sync_desc)
        idaapi.attach_action_to_toolbar(
            "DebugToolBar",
            'g_sync_toogle:action')

    def OnClose(self, form):
        rs_debug("form close")
        self.smooth_kill()

        idaapi.unregister_action('hexrays_sync_toogle:action')
        idaapi.detach_action_from_toolbar(
            "DebugToolBar",
            'hexrays_sync_toogle:action')

        idaapi.unregister_action('g_sync_toogle:action')
        idaapi.detach_action_from_toolbar(
            "DebugToolBar",
            'g_sync_toogle:action')

        global SyncForm
        del SyncForm
        SyncForm = None

    def Show(self):
        return PluginForm.Show(self, "ret-sync", options=PluginForm.WOPN_PERSIST)


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


class RetSyncPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_PROC
    comment = 'Reverse-Engineering Tools synchronization, ret-sync .'
    help = 'Synchronize a debugging session with IDA.'
    wanted_name = 'ret-sync'
    wanted_hotkey = 'Alt-Shift-S'
    global SyncForm
    SyncForm = None

    def init(self):
        return idaapi.PLUGIN_KEEP

    def term(self):
        pass

    def run(self, arg):
        if not idaapi.get_root_filename():
            rs_log('please load a file/idb before')
            return

        global SyncForm
        if not SyncForm:
            SyncForm = SyncForm_t()
            SyncForm.Show()
            rs_log("plugin loaded")


def PLUGIN_ENTRY():
    return RetSyncPlugin()


if __name__ == "__main__":
    rs_log("ret-sync is an IDA Pro plugin, please see README for installation notes")


repository: carsond135/malwareanalysis
file: tools/IDA_Pro_7.7/IDA_Pro_7.7/plugins/SyncPlugin.py
description: NKU恶意代码分析
wanted_name: ret-sync
comment: Reverse-Engineering Tools synchronization, ret-sync .
created_at: 2023-06-25 19:16:29+00:00
pushed_at: 2022-12-18 09:34:14+00:00
url:
https://github.com/carsond135/malwareanalysis/blob/45c8bf862a54239ff1e7c91313eccf9cf7d4a35b/tools/IDA_Pro_7.7/IDA_Pro_7.7/plugins/SyncPlugin.py


repository: vAlerainTech/vAlerain-Ark
file: cmake-build-debug/IDA_Pro_7.7/python/examples/core/auto_instantiate_widget_plugin.py
wanted_name: autoinst
created_at: 2024-03-25 14:26:36+00:00
pushed_at: 2024-07-27 05:07:46+00:00
url:
https://github.com/vAlerainTech/vAlerain-Ark/blob/df8bcafeda091d30ceade7bb2c7c73b5ae351a38/cmake-build-debug/IDA_Pro_7.7/python/examples/core/auto_instantiate_widget_plugin.py

content:
"""
summary: better integrating custom widgets in the desktop layout

description:
  This is an example demonstrating how one can create widgets from a plugin,
  and have them re-created automatically at IDA startup-time or at desktop load-time.

  This example should be placed in the 'plugins' directory of the
  IDA installation, for it to work.

  There are 2 ways to use this example:
  1) reloading an IDB, where the widget was opened
     - open the widget ('View > Open subview > ...')
     - save this IDB, and close IDA
     - restart IDA with this IDB
       => the widget will be visible

  2) reloading a desktop, where the widget was opened
     - open the widget ('View > Open subview > ...')
     - save the desktop ('Windows > Save desktop...') under, say, the name 'with_auto'
     - start another IDA instance with some IDB, and load that desktop
       => the widget will be visible

keywords: desktop
"""

import ida_idaapi
import ida_kernwin

title = "Auto-instantiable at IDA startup"

# -----------------------------------------------------------------------
class auto_inst_t(ida_kernwin.simplecustviewer_t):
    def __init__(self):
        ida_kernwin.simplecustviewer_t.__init__(self)

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

        text = __doc__
        for l in text.split("\n"):
            self.AddLine(l)
        return True

# -----------------------------------------------------------------------
auto_inst = None

# -----------------------------------------------------------------------
def register_open_action():
    """
    Provide the action that will create the widget
    when the user asks for it.
    """
    class create_widget_t(ida_kernwin.action_handler_t):
        def activate(self, ctx):
            if ida_kernwin.find_widget(title) is None:
                global auto_inst
                auto_inst = auto_inst_t()
                assert(auto_inst.Create())
                assert(auto_inst.Show())

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

    action_name = "autoinst:create"
    ida_kernwin.register_action(
        ida_kernwin.action_desc_t(
            action_name,
            title,
            create_widget_t()))
    ida_kernwin.attach_action_to_menu(
        "View/Open subviews/Strings",
        action_name,
        ida_kernwin.SETMENU_APP)

# -----------------------------------------------------------------------
auto_inst_hooks = None
def register_autoinst_hooks():
    """
    Register hooks that will create the widget when IDA
    requires it because of the IDB/desktop
    """
    class auto_inst_hooks_t(ida_kernwin.UI_Hooks):
        def create_desktop_widget(self, ttl, cfg):
            if ttl == title:
                global auto_inst
                auto_inst = auto_inst_t()
                assert(auto_inst.Create())
                return auto_inst.GetWidget()

    global auto_inst_hooks
    auto_inst_hooks = auto_inst_hooks_t()
    auto_inst_hooks.hook()

# -----------------------------------------------------------------------
class auto_inst_plugin_t(ida_idaapi.plugin_t):
    flags = 0
    comment = "This plugin creates a widget that will be recreated automatically if needed, either at startup or when loading a desktop that requires it"
    help = "No help, really"
    wanted_name = "autoinst"
    wanted_hotkey = ""

    def init(self):
        register_open_action()
        register_autoinst_hooks()

    def run(self, arg):
        pass

    def term(self):
        pass


def PLUGIN_ENTRY():
    return auto_inst_plugin_t()


repository: BraveCattle/ctf
file: tools/ida_pro/IDA_Pro_7.7/python/examples/core/auto_instantiate_widget_plugin.py
wanted_name: autoinst
created_at: 2024-01-23 08:17:51+00:00
pushed_at: 2024-10-23 09:31:30+00:00
url:
https://github.com/BraveCattle/ctf/blob/5858d0da5c048eb8468ff8be1d63fbc50d61b601/tools/ida_pro/IDA_Pro_7.7/python/examples/core/auto_instantiate_widget_plugin.py

content:
"""
summary: better integrating custom widgets in the desktop layout

description:
  This is an example demonstrating how one can create widgets from a plugin,
  and have them re-created automatically at IDA startup-time or at desktop load-time.

  This example should be placed in the 'plugins' directory of the
  IDA installation, for it to work.

  There are 2 ways to use this example:
  1) reloading an IDB, where the widget was opened
     - open the widget ('View > Open subview > ...')
     - save this IDB, and close IDA
     - restart IDA with this IDB
       => the widget will be visible

  2) reloading a desktop, where the widget was opened
     - open the widget ('View > Open subview > ...')
     - save the desktop ('Windows > Save desktop...') under, say, the name 'with_auto'
     - start another IDA instance with some IDB, and load that desktop
       => the widget will be visible

keywords: desktop
"""

import ida_idaapi
import ida_kernwin

title = "Auto-instantiable at IDA startup"

# -----------------------------------------------------------------------
class auto_inst_t(ida_kernwin.simplecustviewer_t):
    def __init__(self):
        ida_kernwin.simplecustviewer_t.__init__(self)

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

        text = __doc__
        for l in text.split("\n"):
            self.AddLine(l)
        return True

# -----------------------------------------------------------------------
auto_inst = None

# -----------------------------------------------------------------------
def register_open_action():
    """
    Provide the action that will create the widget
    when the user asks for it.
    """
    class create_widget_t(ida_kernwin.action_handler_t):
        def activate(self, ctx):
            if ida_kernwin.find_widget(title) is None:
                global auto_inst
                auto_inst = auto_inst_t()
                assert(auto_inst.Create())
                assert(auto_inst.Show())

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

    action_name = "autoinst:create"
    ida_kernwin.register_action(
        ida_kernwin.action_desc_t(
            action_name,
            title,
            create_widget_t()))
    ida_kernwin.attach_action_to_menu(
        "View/Open subviews/Strings",
        action_name,
        ida_kernwin.SETMENU_APP)

# -----------------------------------------------------------------------
auto_inst_hooks = None
def register_autoinst_hooks():
    """
    Register hooks that will create the widget when IDA
    requires it because of the IDB/desktop
    """
    class auto_inst_hooks_t(ida_kernwin.UI_Hooks):
        def create_desktop_widget(self, ttl, cfg):
            if ttl == title:
                global auto_inst
                auto_inst = auto_inst_t()
                assert(auto_inst.Create())
                return auto_inst.GetWidget()

    global auto_inst_hooks
    auto_inst_hooks = auto_inst_hooks_t()
    auto_inst_hooks.hook()

# -----------------------------------------------------------------------
class auto_inst_plugin_t(ida_idaapi.plugin_t):
    flags = 0
    comment = "This plugin creates a widget that will be recreated automatically if needed, either at startup or when loading a desktop that requires it"
    help = "No help, really"
    wanted_name = "autoinst"
    wanted_hotkey = ""

    def init(self):
        register_open_action()
        register_autoinst_hooks()

    def run(self, arg):
        pass

    def term(self):
        pass


def PLUGIN_ENTRY():
    return auto_inst_plugin_t()


repository: carsond135/malwareanalysis
file: tools/IDA_Pro_7.7/IDA_Pro_7.7/python/examples/core/auto_instantiate_widget_plugin.py
description: NKU恶意代码分析
wanted_name: autoinst
created_at: 2023-06-25 19:16:29+00:00
pushed_at: 2022-12-18 09:34:14+00:00
url:
https://github.com/carsond135/malwareanalysis/blob/45c8bf862a54239ff1e7c91313eccf9cf7d4a35b/tools/IDA_Pro_7.7/IDA_Pro_7.7/python/examples/core/auto_instantiate_widget_plugin.py


repository: HACK-THE-WORLD/IDAPluginList
file: Finger/finger_plugin.py
description: IDA插件集合，含项目名称及项目地址，每日定时Clone项目。
wanted_name: Finger
created_at: 2022-03-21 02:05:27+00:00
pushed_at: 2024-09-21 02:08:36+00:00
url:
https://github.com/HACK-THE-WORLD/IDAPluginList/blob/a6367e2126fca45260d1052b0c54bd332490fd60/Finger/finger_plugin.py

content:
import idc
import idaapi
import idautils
import traceback
from finger_sdk import client, ida_func


class FingerManager:
    def __init__(self):
        self.url = "https://sec-lab.aliyun.com/finger/recognize/"
        self.headers = {'content-type': 'application/json'}
        self.timeout = 5
        self.client = None


    def recognize_function(self, start_ea):
        func_symbol = None
        try:
            self.client = client.Client(self.url, self.headers, self.timeout)
            func_feat = ida_func.get_func_feature(start_ea)
            if func_feat:
                func_id, res = self.client.recognize_function(func_feat)
                if res and res[func_id]:
                    func_symbol = res[func_id]
        except Exception as e:
            print(traceback.format_exc())
        if func_symbol:
            func_symbol = str(func_symbol)  # python2 unicode to str
        return func_symbol


    def recognize_selected_function(self, funcs):
        for pfn in funcs:
            func_name = idc.get_func_name(pfn.start_ea)
            func_symbol = self.recognize_function(pfn.start_ea)
            if func_symbol:
                idc.set_color(pfn.start_ea, idc.CIC_FUNC, 0x98FF98)
                idaapi.set_name(pfn.start_ea, func_symbol, idaapi.SN_FORCE)
                idaapi.update_func(pfn)
                print("[+]Recognize %s: %s" %(func_name, func_symbol))
            else:
                print("[-]%s recognize failed" %(func_name))


    def recognize_function_callback(self, menupath):
        ea = idaapi.get_screen_ea()
        pfn = idaapi.get_func(ea)
        if pfn:
            func_name = idc.get_func_name(pfn.start_ea)
            func_symbol = self.recognize_function(pfn.start_ea)
            if func_symbol:
                idc.set_color(pfn.start_ea, idc.CIC_FUNC, 0x98FF98)
                idaapi.set_name(pfn.start_ea, func_symbol, idaapi.SN_FORCE)
                idaapi.update_func(pfn)
                print("[+]Recognize %s: %s" %(func_name, func_symbol))
            else:
                print("[-]%s recognize failed" %(func_name))
        else:
            print("[-]0x%x is not a function" %ea)


    def recognize_functions_callback(self, menupath):
        funcs = []
        for ea in idautils.Functions():
            funcs.append(idaapi.get_func(ea))
        self.recognize_selected_function(funcs)


class FingerUIManager:
    class UIHooks(idaapi.UI_Hooks):
        def finish_populating_widget_popup(self, widget, popup):
            if idaapi.get_widget_type(widget) == idaapi.BWN_FUNCS:
                idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeSelected", "Finger/")
            if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM:
                idaapi.attach_action_to_popup(widget, popup, "Finger:RecognizeFunction", "Finger/")


    class ActionHandler(idaapi.action_handler_t):
        def __init__(self, name, label, shortcut=None, tooltip=None, icon=-1, flags=0):
            idaapi.action_handler_t.__init__(self)
            self.name = name
            self.action_desc = idaapi.action_desc_t(name, label, self, shortcut, tooltip, icon, flags)

        def register_action(self, callback,  menupath=None):
            self.callback = callback
            if not idaapi.register_action(self.action_desc):
                return False
            if menupath and not idaapi.attach_action_to_menu(menupath, self.name, idaapi.SETMENU_APP):
                return False
            return True

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

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


    def __init__(self, name):
        self.name = name
        self.mgr = FingerManager()
        self.hooks = FingerUIManager.UIHooks()

    def register_actions(self):
        menupath = self.name
        idaapi.create_menu(menupath, self.name, "Help")

        action = FingerUIManager.ActionHandler("Finger:RecognizeFunctions", "Recognize all functions", "")
        action.register_action(self.mgr.recognize_functions_callback, menupath)
        action = FingerUIManager.ActionHandler("Finger:RecognizeFunction", "Recognize function", "")
        action.register_action(self.mgr.recognize_function_callback, menupath)
        recognize_action = FingerUIManager.ActionHandler("Finger:RecognizeSelected", "Recognize function")
        if recognize_action.register_action(self.selected_function_callback):
            self.hooks.hook()
            return True
        return False


    def selected_function_callback(self, ctx):
        funcs = map(idaapi.getn_func, ctx.chooser_selection)
        if ctx.action == "Finger:RecognizeSelected":
            self.mgr.recognize_selected_function(funcs)


def check_ida_version():
    if idaapi.IDA_SDK_VERSION < 700:
        print("[-]Finger support 7.x IDA, please update your IDA version.")
        return False
    return True


class FingerPlugin(idaapi.plugin_t):
    wanted_name = "Finger"
    comment, help, wanted_hotkey = "", "", ""
    flags = idaapi.PLUGIN_FIX | idaapi.PLUGIN_HIDE | idaapi.PLUGIN_MOD

    def init(self):
        if check_ida_version():
            idaapi.msg("[+]Finger plugin starts\n")
            manager = FingerUIManager(FingerPlugin.wanted_name)
            if manager.register_actions():
                return idaapi.PLUGIN_OK
        return idaapi.PLUGIN_SKIP

    def run(self, ctx):
        return

    def term(self):
        return


def PLUGIN_ENTRY():
    return FingerPlugin()


repository: mandiant/capa
file: capa/ida/plugin/capa_explorer.py
description: The FLARE team's open-source tool to identify capabilities in executable files.
created_at: 2020-06-16 21:24:57+00:00
pushed_at: 2025-05-12 21:11:22+00:00
url:
https://github.com/mandiant/capa/blob/3a9f2136bbba4bab8d3f23831f73d57fef999b55/capa/ida/plugin/capa_explorer.py

content:
# Copyright 2020 Google LLC
#
# 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.


from capa.ida.plugin import CapaExplorerPlugin


def PLUGIN_ENTRY():
    """mandatory entry point for IDAPython plugins

    copy this script to your IDA plugins directory and start the plugin by navigating to Edit > Plugins in IDA Pro
    """
    return CapaExplorerPlugin()


repository: mzakocs/VirtualizationObfuscatorAnalysis
file: ida/plugins/nop_and_hide.py
wanted_name: NOPnHIDE
comment: NOPS and HIDES an Instruction
created_at: 2021-06-14 20:38:55+00:00
pushed_at: 2022-10-07 18:35:05+00:00
description:
A repository of IDA Databases and Binaries used for the analysis of popular commercial virtual-machine obfuscators

url:
https://github.com/mzakocs/VirtualizationObfuscatorAnalysis/blob/2fb7ad84a90a73c950c39eccbad81cc2f6c25c33/ida/plugins/nop_and_hide.py

content:
import idaapi
import ida_ua
import ida_bytes
import idc

class NOPnHIDEPlugin(idaapi.plugin_t):
    # variables required by IDA
    flags = 0  # normal plugin
    wanted_name = "NOPnHIDE"
    wanted_hotkey = "Ctrl-Shift-N"
    comment = "NOPS and HIDES an Instruction"
    help = ""
    initialized = False

    # IDA API methods: init, run, term
    def init(self):
        print("NOPnHIDE initialized!")
        return idaapi.PLUGIN_OK


    def run(self, args):
        # Does this transformation wherever the cursor is
        address = idc.here()    
        # Grabs instruction info at the cursor
        ins = ida_ua.insn_t()    
        # Decodes it and grabs its size
        idaapi.decode_insn(ins, address)
        size = ins.size
        # Undefine the data here so we can modify instructions
        ida_bytes.del_items(address)
        # Modify all of the bytes to 0
        print("Hiding: " + ins.get_canon_mnem())
        for byte in range(0, size):
            idc.patch_byte(address + byte, 0x90)
            ida_ua.create_insn(address + byte)
        # Hides the bytes
        print("Start Address: " + str(address))
        print("End Address: " + str(address + size))
        ida_bytes.add_hidden_range(address, address + size, "", "hidden nop", "hidden nop", 0x2d2d2d)


    def term(self):
        print("Terminating NOPnHIDE...")
        self.initialized = False


def PLUGIN_ENTRY():
    return NOPnHIDEPlugin()


repository: idapython/src
file: examples/decompiler/vds21.py
description: IDAPython project for Hex-Ray's IDA Pro
wanted_name: Hex-Rays custom prototype provider (IDAPython)
comment: Sample plugin21 for Hex-Rays decompiler
created_at: 2015-03-14 20:09:27+00:00
pushed_at: 2025-03-13 15:18:11+00:00
url:
https://github.com/idapython/src/blob/af978e79e68af701c45bab401afce5145aa4b80b/examples/decompiler/vds21.py

content:
"""
summary: dynamically provide a custom call type

description:
  This plugin can greatly improve decompilation of indirect calls:

      call    [eax+4]

  For them, the decompiler has to guess the prototype of the called function.
  This has to be done at a very early phase of decompilation because
  the function prototype influences the data flow analysis. On the other
  hand, we do not have global data flow analysis results yet because
  we haven't analyzed all calls in the function. It is a chicked-and-egg
  problem.

  The decompiler uses various techniques to guess the called function
  prototype. While it works very well, it may fail in some cases.

  To fix, the user can specify the call prototype manually, using
  "Edit, Operand types, Set operand type" at the call instruction.

  This plugin illustrates another approach to the problem:
  if you happen to be able to calculate the call prototypes dynamically,
  this is how to inform the decompiler about them.

level: intermediate
"""

import ida_idaapi
import ida_nalt
import ida_kernwin
import ida_typeinf
import ida_hexrays

testing = False # only for testing purposes

class callinfo_provider_t(ida_hexrays.Hexrays_Hooks):

    # this callback will be called for all call instructions
    # our plugin may provide the function prototype or even a complete new callinfo
    # object. The callinfo object may be useful if the prototype is not enough
    # to express all details of the call.
    def build_callinfo(self, blk, type):
        # it is a good idea to skip direct calls.
        # note that some indirect calls may be resolved and become direct calls,
        # and will be filtered out here:
        ida_kernwin.msg("%x: got called for: %s\n" % (blk.tail.ea, blk.tail.dstr()))
        tail = blk.tail
        if tail.opcode == ida_hexrays.m_call:
            return

        # also, if the type was specified by the user, do not interfere
        call_ea = tail.ea
        tif = ida_typeinf.tinfo_t()
        if ida_nalt.get_op_tinfo(tif, call_ea, 0):
            return

        global testing
        if not testing:
            # ok, the decompiler really has to guess the type.
            # just for the sake of an example, return a predefined prototype.
            # in real life you will provide the prototype you discovered yourself,
            # using your magic of yours :)
            my_proto = "int f();"
            ida_kernwin.msg("%x: providing prototype %s\n" % (call_ea, my_proto))
            ida_typeinf.parse_decl(type, None, my_proto, 0)
        else:
            # as an alternative to filling 'type', you can
            # choose to return a mcallinfo_t instance.
            mi = ida_hexrays.mcallinfo_t()
            mi.cc = ida_typeinf.CM_CC_STDCALL | ida_typeinf.CM_N32_F48 # let's use stdcall to differentiate from the tinfo_t-filling version
            mi.return_type = ida_typeinf.tinfo_t(ida_typeinf.BT_INT)
            mi.return_argloc.set_reg1(0) # eax
            return mi

# a plugin interface, boilerplate code
class my_plugin_t(ida_idaapi.plugin_t):
    flags = ida_idaapi.PLUGIN_HIDE
    wanted_name = "Hex-Rays custom prototype provider (IDAPython)"
    wanted_hotkey = ""
    comment = "Sample plugin21 for Hex-Rays decompiler"
    help = ""
    def init(self):
        if ida_hexrays.init_hexrays_plugin():
            self.hooks = callinfo_provider_t()
            self.hooks.hook()
            ida_kernwin.warning(
                "Installed callinfo provider sample (vds21.py)\n" +\
                "Please note that it is just an example\n" +\
                "and will spoil your decompilations!")
            return ida_idaapi.PLUGIN_KEEP # keep us in the memory
    def term(self):
        self.hooks.unhook()
    def run(self, arg):
        pass

def PLUGIN_ENTRY():
    return my_plugin_t()


repository: joxeankoret/diaphora
file: plugin/diaphora_plugin.py
description: Diaphora, the most advanced Free and Open Source program diffing tool.
wanted_name: Diaphora
comment: Diaphora by joxeankoret
created_at: 2015-03-20 02:12:26+00:00
pushed_at: 2024-11-24 08:49:59+00:00
url:
https://github.com/joxeankoret/diaphora/blob/d8b898e35750d103cb6a15f7a87910ff6b237427/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: c3rb3ru5d3d53c/binlex
file: scripts/plugins/ida/binlex/main.py
description: A Binary Genetic Traits Lexer Framework
wanted_name: Binlex
comment: Binlex IDA Plugin
created_at: 2021-06-14 22:46:09+00:00
pushed_at: 2025-02-23 22:09:15+00:00
url:
https://github.com/c3rb3ru5d3d53c/binlex/blob/9313b92d1976c58021111a3d649396b1e2bc8f81/scripts/plugins/ida/binlex/main.py

content:
#!/usr/bin/env python

#                    GNU LESSER GENERAL PUBLIC LICENSE
#                        Version 3, 29 June 2007
#
#  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
#  Everyone is permitted to copy and distribute verbatim copies
#  of this license document, but changing it is not allowed.
#
#
#   This version of the GNU Lesser General Public License incorporates
# the terms and conditions of version 3 of the GNU General Public
# License, supplemented by the additional permissions listed below.
#
#   0. Additional Definitions.
#
#   As used herein, "this License" refers to version 3 of the GNU Lesser
# General Public License, and the "GNU GPL" refers to version 3 of the GNU
# General Public License.
#
#   "The Library" refers to a covered work governed by this License,
# other than an Application or a Combined Work as defined below.
#
#   An "Application" is any work that makes use of an interface provided
# by the Library, but which is not otherwise based on the Library.
# Defining a subclass of a class defined by the Library is deemed a mode
# of using an interface provided by the Library.
#
#   A "Combined Work" is a work produced by combining or linking an
# Application with the Library.  The particular version of the Library
# with which the Combined Work was made is also called the "Linked
# Version".
#
#   The "Minimal Corresponding Source" for a Combined Work means the
# Corresponding Source for the Combined Work, excluding any source code
# for portions of the Combined Work that, considered in isolation, are
# based on the Application, and not on the Linked Version.
#
#   The "Corresponding Application Code" for a Combined Work means the
# object code and/or source code for the Application, including any data
# and utility programs needed for reproducing the Combined Work from the
# Application, but excluding the System Libraries of the Combined Work.
#
#   1. Exception to Section 3 of the GNU GPL.
#
#   You may convey a covered work under sections 3 and 4 of this License
# without being bound by section 3 of the GNU GPL.
#
#   2. Conveying Modified Versions.
#
#   If you modify a copy of the Library, and, in your modifications, a
# facility refers to a function or data to be supplied by an Application
# that uses the facility (other than as an argument passed when the
# facility is invoked), then you may convey a copy of the modified
# version:
#
#    a) under this License, provided that you make a good faith effort to
#    ensure that, in the event an Application does not supply the
#    function or data, the facility still operates, and performs
#    whatever part of its purpose remains meaningful, or
#
#    b) under the GNU GPL, with none of the additional permissions of
#    this License applicable to that copy.
#
#   3. Object Code Incorporating Material from Library Header Files.
#
#   The object code form of an Application may incorporate material from
# a header file that is part of the Library.  You may convey such object
# code under terms of your choice, provided that, if the incorporated
# material is not limited to numerical parameters, data structure
# layouts and accessors, or small macros, inline functions and templates
# (ten or fewer lines in length), you do both of the following:
#
#    a) Give prominent notice with each copy of the object code that the
#    Library is used in it and that the Library and its use are
#    covered by this License.
#
#    b) Accompany the object code with a copy of the GNU GPL and this license
#    document.
#
#   4. Combined Works.
#
#   You may convey a Combined Work under terms of your choice that,
# taken together, effectively do not restrict modification of the
# portions of the Library contained in the Combined Work and reverse
# engineering for debugging such modifications, if you also do each of
# the following:
#
#    a) Give prominent notice with each copy of the Combined Work that
#    the Library is used in it and that the Library and its use are
#    covered by this License.
#
#    b) Accompany the Combined Work with a copy of the GNU GPL and this license
#    document.
#
#    c) For a Combined Work that displays copyright notices during
#    execution, include the copyright notice for the Library among
#    these notices, as well as a reference directing the user to the
#    copies of the GNU GPL and this license document.
#
#    d) Do one of the following:
#
#        0) Convey the Minimal Corresponding Source under the terms of this
#        License, and the Corresponding Application Code in a form
#        suitable for, and under terms that permit, the user to
#        recombine or relink the Application with a modified version of
#        the Linked Version to produce a modified Combined Work, in the
#        manner specified by section 6 of the GNU GPL for conveying
#        Corresponding Source.
#
#        1) Use a suitable shared library mechanism for linking with the
#        Library.  A suitable mechanism is one that (a) uses at run time
#        a copy of the Library already present on the user's computer
#        system, and (b) will operate properly with a modified version
#        of the Library that is interface-compatible with the Linked
#        Version.
#
#    e) Provide Installation Information, but only if you would otherwise
#    be required to provide such information under section 6 of the
#    GNU GPL, and only to the extent that such information is
#    necessary to install and execute a modified version of the
#    Combined Work produced by recombining or relinking the
#    Application with a modified version of the Linked Version. (If
#    you use option 4d0, the Installation Information must accompany
#    the Minimal Corresponding Source and Corresponding Application
#    Code. If you use option 4d1, you must provide the Installation
#    Information in the manner specified by section 6 of the GNU GPL
#    for conveying Corresponding Source.)
#
#   5. Combined Libraries.
#
#   You may place library facilities that are a work based on the
# Library side by side in a single library together with other library
# facilities that are not Applications and are not covered by this
# License, and convey such a combined library under terms of your
# choice, if you do both of the following:
#
#    a) Accompany the combined library with a copy of the same work based
#    on the Library, uncombined with any other library facilities,
#    conveyed under the terms of this License.
#
#    b) Give prominent notice with the combined library that part of it
#    is a work based on the Library, and explaining where to find the
#    accompanying uncombined form of the same work.
#
#   6. Revised Versions of the GNU Lesser General Public License.
#
#   The Free Software Foundation may publish revised and/or new versions
# of the GNU Lesser General Public License from time to time. Such new
# versions will be similar in spirit to the present version, but may
# differ in detail to address new problems or concerns.
#
#   Each version is given a distinguishing version number. If the
# Library as you received it specifies that a certain numbered version
# of the GNU Lesser General Public License "or any later version"
# applies to it, you have the option of following the terms and
# conditions either of that published version or of any later version
# published by the Free Software Foundation. If the Library as you
# received it does not specify a version number of the GNU Lesser
# General Public License, you may choose any version of the GNU Lesser
# General Public License ever published by the Free Software Foundation.
#
#   If the Library as you received it specifies that a proxy can decide
# whether future versions of the GNU Lesser General Public License shall
# apply, that proxy's public statement of acceptance of any version is
# permanent authorization for you to choose that version for the
# Library.

import idc
import idaapi
import ida_bytes
import idautils
import ida_kernwin
import ida_ida
import json
import os
from binlex.formats import (
    File,
    PE,
    ELF
)
from binlex.hashing import (
    MinHash32,
    TLSH
)
from binlex import (
    Config,
    Architecture
)
from binlex.controlflow import (
    Graph,
    Instruction,
    FunctionJsonDeserializer,
    BlockJsonDeserializer,
)
from binlex.genetics import Chromosome
from binlex.disassemblers.capstone import Disassembler as CapstoneDisassembler
from binlex.types import MemoryMappedFile
from PyQt5.QtWidgets import QApplication, QDialog
import tempfile
from lib import IDA
from gui.hooks import UIHooks
from gui import (
    register_action_handlers,
    unregister_action_handlers,
    Main,
    SVGWidget,
    Progress,
    JSONSearchWindow,
    OkayCancelDialog,
    About,
)
from lib.text import BANNER
from actions import (
    copy_pattern,
    copy_hex,
    scan_minhash,
    copy_minhash,
    copy_tlsh,
    scan_tlsh,
    function_table,
    search_database,
    index_database,
    export,
    export_byte_colormap,
    copy_block_json,
    copy_block_vector,
    copy_function_vector,
    copy_function_json,
    index_function,
    index_block,
)
from binlex.imaging import ColorMap

class BinlexPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_DRAW
    comment = 'Binlex IDA Plugin'
    help = 'A Binary Genetic Trait Lexer Framework'
    wanted_name = 'Binlex'

    def init(self):
        self.config = Config()
        try:
            self.config.write_default()
        except:
            pass
        self.config.from_default()
        self.main_window = None
        self.about_window = None
        self.function_table_window = None
        self.json_search_window = None
        self.is_disassembled = False
        self.ui_hooks = UIHooks(self)
        self.ui_hooks.hook()
        self.ida = IDA()
        register_action_handlers(self)
        self.load_binary()
        ida_kernwin.msg(BANNER + '\n')
        return idaapi.PLUGIN_KEEP

    def action_copy_pattern(self):
        copy_pattern(self)

    def action_copy_hex(self):
        copy_hex(self)

    def action_copy_tlsh(self):
        copy_tlsh(self)

    def action_copy_minhash(self):
        copy_minhash(self)

    def action_index_function(self):
        index_function(self)

    def action_index_block(self):
        index_block(self)

    def _disassemble_controlflow(self):
        number_of_functions = len(IDA.get_functions())
        progress = Progress(title='Disassembling CFG', max_value=number_of_functions)
        progress.show()
        for function in IDA.get_functions():
            progress.increment()
            self.disassemble_function(function)
        progress.close()

    def disassemble_function(self, function):
        for block in IDA.get_function_blocks(function):
            self.disassemble_block(block)

    def disassemble_block(self, block):
        to = set([bb.start_ea for bb in block.succs()])
        for instruction in IDA.get_block_instructions(block):
            self.disassemble_instruction(instruction)
            if IDA().is_function_ea(instruction.ea):
                self.cfg.set_function(instruction.ea)
            if block.start_ea == instruction.ea:
                self.cfg.set_block(instruction.ea)
            if idc.prev_head(block.end_ea) == instruction.ea:
                self.cfg.extend_instruction_edges(instruction.ea, to)

    def disassemble_instruction(self, instruction):
        self.disassembler.disassemble_instruction(instruction.ea, self.cfg)

    def load_disassembler(self):
        self.disassembler = CapstoneDisassembler(self.architecture, self.image, self.executable_address_ranges, self.config)

    def action_copy_block_vector(self):
        copy_block_vector(self)

    def action_copy_block_json(self):
        copy_block_json(self)

    def action_copy_function_vector(self):
        copy_function_vector(self)

    def action_copy_function_json(self):
        copy_function_json(self)

    def update(self, ctx):
        if ctx.widget:
            widget_type = ida_kernwin.get_widget_type(ctx.widget)
            ida_kernwin.msg(f"[*] Widget type: {widget_type}\n")
            if widget_type == ida_kernwin.BWN_DISASM:
                return ida_kernwin.AST_ENABLE_ALWAYS
        return ida_kernwin.AST_DISABLE

    @staticmethod
    def get_function_symbol_attribute(ea: int):
        attribute = {}
        attribute['type'] = 'symbol'
        attribute['symbol_type'] = 'function'
        attribute['file_offset'] = None
        attribute['relative_virtual_address'] = None
        attribute['virtual_address'] = ea
        attribute['name'] = IDA.get_function_name(ea)
        attribute['slice'] = None
        return attribute

    def disassemble_controlflow(self):
        if not self.is_disassembled:
            self._disassemble_controlflow()
            self.is_disassembled = True
            return

        dialog = OkayCancelDialog(title='Disassemble Again?', okay_text='Disassemble', cancel_text='Continue')
        if dialog.exec_() == QDialog.Accepted:
            self._disassemble_controlflow()

    def action_export(self):
        export(self)

    def load_image(self):
        directory = os.path.join(tempfile.gettempdir(), 'binlex')
        if not os.path.exists(directory): os.makedirs(directory)
        file_path = os.path.join(directory, IDA().get_database_sha256())
        self.mapped_file = MemoryMappedFile(file_path, False)
        for segment in idautils.Segments():
            start = idc.get_segm_start(segment)
            end = idc.get_segm_end(segment)
            data = ida_bytes.get_bytes(start, end - start)
            if data is None: continue
            if self.mapped_file.size() < start:
                self.mapped_file.seek_to_end()
                self.mapped_file.write_padding(start - self.mapped_file.size())
            self.mapped_file.seek_to_end()
            self.mapped_file.write(data)
        self.executable_address_ranges = {0: self.mapped_file.size()}
        self.image = self.mapped_file.mmap()

    def load_binary(self) -> bool:
        if ida_ida.inf_get_procname() == 'metapc':
            if ida_ida.inf_is_32bit_exactly() is True:
                self.architecture = Architecture.from_str('i386')
                self.cfg = Graph(self.architecture, self.config)
                self.load_image()
                self.load_disassembler()
                return True
            else:
                self.architecture = Architecture.from_str('amd64')
                self.cfg = Graph(self.architecture, self.config)
                self.load_image()
                self.load_disassembler()
                return True
        return False

    def open_main_window(self):
        if not self.main_window:
            self.main_window = Main(self)
        self.main_window.show()

    def open_about_window(self):
        if not self.about_window:
            self.about_window = About(self)
        self.about_window.exec_()

    def action_scan_tlsh(self):
        scan_tlsh(self)

    def action_scan_minhash(self):
        scan_minhash(self)

    def action_search_database(self):
        search_database(self)

    def action_json_search_window(self):
        if not self.json_search_window:
            self.disassemble_controlflow()
            data = [json.loads(function.json()) for function in self.cfg.functions()]
            data.extend([json.loads(block.json()) for block in self.cfg.blocks()])
            self.json_search_window = JSONSearchWindow(json_objects=data)
        self.json_search_window.Show('Binlex JSON Search')

    def action_export_byte_colormap(self):
        export_byte_colormap(self)

    def get_function_attributes(self) -> dict:
        results = {}
        for address in self.cfg.queue_functions.valid_addresses():
            attributes = []
            attributes.append(IDA.file_attribute())
            attributes.append(self.get_function_symbol_attribute(address))
            results[address] = attributes
        return results

    def get_block_attributes(self) -> dict:
        results = {}
        for address in self.cfg.queue_blocks.valid_addresses():
            attributes = []
            attributes.append(IDA.file_attribute())
            results[address] = attributes
        return results

    def action_index_database(self):
        index_database(self)

    def action_function_table(self):
        function_table(self)

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

    def term(self):
        self.ui_hooks.unhook()
        unregister_action_handlers()

def PLUGIN_ENTRY():
    return BinlexPlugin()


