给IDA写个小插件

给ida写个插件

1.插件功能

找函数尾巴
在这里插入图片描述

arm64汇编批量nop

在这里插入图片描述

我们来看一个最简单的插件

# 引入 idaapi 模块,这是 IDA Pro 的 Python API
import idaapi

# 定义一个 MyPlugin 类,它继承自 idaapi.plugin_t。这个类将定义插件的行为。
class MyPlugin(idaapi.plugin_t):
    # 插件的标志。这里我们没有设置任何标志,所以它是 0。
    flags = 0

    # 插件的注释。这将在 IDA Pro 的插件列表中显示。
    comment = "This is a comment about the plugin"

    # 插件的帮助文本。这将在用户请求插件的帮助时显示。
    help = "This is help text for the plugin"

    # 插件的名字。这将在 IDA Pro 的插件列表中显示。
    wanted_name = "My Plugin"

    # 插件的热键。用户可以按这个键来运行插件。
    wanted_hotkey = "Alt-F8"

    # init 方法在插件被加载时调用。在这里,我们只是在输出窗口打印一条消息。
    def init(self):
        idaapi.msg("MyPlugin: Initialized\n")
        return idaapi.PLUGIN_OK

    # run 方法在插件被执行时调用。在这里,我们只是在输出窗口打印一条消息。
    def run(self, arg):
        idaapi.msg("MyPlugin: Run\n")

    # term 方法在插件被卸载时调用。在这里,我们只是在输出窗口打印一条消息。
    def term(self):
        idaapi.msg("MyPlugin: Terminated\n")

# PLUGIN_ENTRY 函数是插件的入口点。它返回一个插件对象的实例。
def PLUGIN_ENTRY():
    return MyPlugin()

在代码的注释中我们看到,当插件注入的时候,在插件被调用、卸载ida去触发一些方法,从而来控制整个流程。

2.插件编写逻辑

为了让python具有能够提示ida中的一些api
我们需要把ida目录中的这个目录
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=.%2FFILES%2F%E7%BB%99ida%E5%86%99%E4%B8%AA%E6%8F%92%E4%BB%B6.md%2Fimg-20230906092440.png&pos_id=img-kmjS66n2-1693967626131
)
复制到
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上

这个时候在pycharm里面编写代码的时候,就可以很愉快的用代码提示了。

接下来我们要实现功能:

我们实现的功能是 主要有两个,一个是寻找函数尾巴,第二个是批量选择指令,然后进行批量nop。

当然,他们是限制在arm64的指令下,

右击指令窗口,拥有选择插件的功能。

然后插件如下方所示

from PyQt5 import QtWidgets, QtCore
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtCore, QtGui, QtWidgets

from capstone import *
from keystone import *

import idaapi
import ida_bytes
import ida_funcs
import ida_name
from ida_bytes import get_bytes, patch_byte, patch_dword
from idautils import CodeRefsTo

ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
md = Cs(CS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)

ACTION_FUNEND = "bestida:findfucend"
ACTION_FILLNOP = "bestida:fillnop"

def ks_disasm(dis_str):
    global ks
    encoding, count = ks.asm(dis_str)
    return encoding


def hex_cleaner(s):
    s = s.strip()
    s = s.replace("0x", "")
    s = s.replace("h", "")
    s = s.replace("L", "")
    return s


class menu_action_handler_t(idaapi.action_handler_t):

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

    def activate(self, ctx):
        if self.action == ACTION_FUNEND:
            # 获得选中的开始地址和结束地址
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1):
                start, end = t0.place(view).toea(), t1.place(view).toea()
                size = end - start
                print("start %s ,end %s" % (start, end))
                self.findfuncend(start, size)
            else:
                print("选择不正确,请选择栈操作空间的部分")
        elif self.action == ACTION_FILLNOP:
            t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer()
            if idaapi.read_selection(view, t0, t1):
                start, end = t0.place(view).toea(), t1.place(view).toea()
                #这个时候说明选中了一段代码
                if (start == end):
                    idaapi.patch_bytes(start, b"\x1f\x20\x03\xd5")
                    print("%s处填充了一条nop" % hex(start))
                else:
                    #arm64代码指令块为四字节
                    if(end - start) % 4 != 0:
                        print("请选中4字节对齐的代码")
                    else:
                        for i in range(start, end, 4):
                            idaapi.patch_bytes(i, b"\x1f\x20\x03\xd5")
                        print("start %s ,end %s" % (hex(start), hex(end)))
                print("start %s ,end %s" % (hex(start), hex(end)))
                #idaapi.patch_bytes(start, b"\x90" * (end - start))


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

    def get_inv_opcode(self, insn):
        return '{} {}'.format(self.ins_map.get(insn.mnemonic), insn.op_str)

    def find_sublist_addr(self, lst, sublist):
        sub_len = len(sublist)
        indices = []
        for index in (i for i, e in enumerate(lst) if e == sublist[0]):
            if lst[index:index + sub_len] == sublist:
                indices.append(index)
        return indices

    def find_sublist(self, lst, sublist):
        len_sublist = len(sublist)
        for i in range(len(lst)):
            if lst[i:i + len_sublist] == sublist:
                return i
        return -1

    def findfuncend(self, start, sz):
        self.ins_map = {"sub": "add", "str": "ldr", "stp": "ldp"}

        target = start
        stackOplen = sz
        print("正在计算funcstart %x 所在的函数尾" % (target))
        encodings = [0xC0, 0x03, 0x5F, 0xD6]  # ret 的指令编码
        for i in md.disasm(ida_bytes.get_bytes(target, stackOplen), 0):
            if i.op_str.find('!') == -1:
                # print(f"{i.mnemonic} {i.op_str}")
                encodings = ks_disasm(self.get_inv_opcode(i)) + encodings
            else:

                s_new = i.op_str.replace("]!", "")
                s_new = s_new.replace(", #-", "],")
                dis_str = '{} {}'.format(self.ins_map.get(i.mnemonic), s_new)  # 调用约定的栈平衡指令
                # print(dis_str)
                encodings = ks_disasm(dis_str) + encodings

        opcodelist = list(ida_bytes.get_bytes(target, 0x8000))
        index = self.find_sublist(opcodelist, encodings)
        func_end = target + index  # 函数结尾的地址
        print("func end addr : ", hex(func_end))


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

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

        if form_type == idaapi.BWN_DISASM and (ARCH, BITS) in [
                                                               (idaapi.PLFM_ARM, 64), ]:
            idaapi.attach_action_to_popup(form, popup, ACTION_FUNEND, "二进制科学/")
            idaapi.attach_action_to_popup(form, popup, ACTION_FILLNOP, "二进制科学/")


class MyForm(QDialog):
    def __init__(self, parent=None):
        super(MyForm, self).__init__(parent)
        self.setupUi(self)
        # 点击确定时候找函数尾巴
        self.button_sure.clicked.connect(self.findfuncend)

        # 出入栈对应的指令
        self.ins_map = {"sub": "add", "str": "ldr", "stp": "ldp"}

        # 报错
        self.registered_actions = []

    # 这个弹窗被拉起,如果按esc就会退出,如果按enter,就会执行找函数结尾
    def keyPressEvent(self, event):
        key_code = event.key()
        if key_code == QtCore.Qt.Key_Escape:
            self.close()
        elif key_code == QtCore.Qt.Key_Enter:
            self.findfuncend()

    def get_inv_opcode(self, insn):
        return '{} {}'.format(self.ins_map.get(insn.mnemonic), insn.op_str)

    def find_sublist_addr(self, lst, sublist):
        sub_len = len(sublist)
        indices = []
        for index in (i for i, e in enumerate(lst) if e == sublist[0]):
            if lst[index:index + sub_len] == sublist:
                indices.append(index)
        return indices

    def find_sublist(self, lst, sublist):
        len_sublist = len(sublist)
        for i in range(len(lst)):
            if lst[i:i + len_sublist] == sublist:
                return i
        return -1

    def findfuncend(self):
        target = int(hex_cleaner(self.edit_funstar.text()), 16)
        stackOplen = int(hex_cleaner(self.edit_stacklen.text()), 16)

        print("正在计算funcstart %x 所在的函数尾" % (target))
        encodings = [0xC0, 0x03, 0x5F, 0xD6]  # ret 的指令编码
        for i in md.disasm(ida_bytes.get_bytes(target, stackOplen * 4), 0):
            if i.op_str.find('!') == -1:
                # print(f"{i.mnemonic} {i.op_str}")
                encodings = ks_disasm(self.get_inv_opcode(i)) + encodings
            else:

                s_new = i.op_str.replace("]!", "")
                s_new = s_new.replace(", #-", "],")
                dis_str = '{} {}'.format(self.ins_map.get(i.mnemonic), s_new)
                # print(dis_str)
                encodings = ks_disasm(dis_str) + encodings

        opcodelist = list(ida_bytes.get_bytes(target, 0x8000))
        index = self.find_sublist(opcodelist, encodings)
        func_end = target + index  # 函数结尾的地址
        print("func end addr : ", hex(func_end))

    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(242, 117)
        self.edit_funstar = QtWidgets.QLineEdit(Dialog)
        self.edit_funstar.setGeometry(QtCore.QRect(100, 30, 113, 20))
        self.edit_funstar.setObjectName("edit_funstar")
        self.button_sure = QtWidgets.QPushButton(Dialog)
        self.button_sure.setGeometry(QtCore.QRect(90, 90, 75, 23))
        self.button_sure.setObjectName("button_sure")
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setGeometry(QtCore.QRect(30, 30, 61, 16))
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(Dialog)
        self.label_2.setGeometry(QtCore.QRect(10, 60, 81, 16))
        self.label_2.setObjectName("label_2")
        self.edit_stacklen = QtWidgets.QLineEdit(Dialog)
        self.edit_stacklen.setGeometry(QtCore.QRect(100, 60, 113, 20))
        self.edit_stacklen.setObjectName("edit_stacklen")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "arm64函数尾"))
        self.button_sure.setText(_translate("Dialog", "确定"))
        self.label.setText(_translate("Dialog", "起始地址"))
        self.label_2.setText(_translate("Dialog", "入栈操作长度"))


def PLUGIN_ENTRY():
    return MyIDAPlugin()


class MyIDAPlugin(idaapi.plugin_t):
    flags = idaapi.PLUGIN_KEEP
    comment = "一个帮助逆向分析的插件"
    help = "This is help"
    wanted_name = "二进制科学"
    wanted_hotkey = "Ctrl-F8"

    def init(self):
        idaapi.msg("二进制科学ida插件加载成功\n")

        self.hexrays_inited = False
        self.registered_actions = []
        self.registered_hx_actions = []

        global ARCH
        global BITS
        ARCH = idaapi.ph_get_id()
        info = idaapi.get_inf_structure()
        if info.is_64bit():
            BITS = 64
        elif info.is_32bit():
            BITS = 32
        else:
            BITS = 16
            # Register menu actions
        menu_actions = (
            idaapi.action_desc_t(ACTION_FUNEND, "find fun end", menu_action_handler_t(ACTION_FUNEND), None,
                                 None, 80),
            idaapi.action_desc_t(ACTION_FILLNOP, "Fill with NOPs", menu_action_handler_t(ACTION_FILLNOP), None, None,
                                 9),
        )

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

        # Add ui hook
        self.ui_hook = UI_Hook()
        self.ui_hook.hook()
        # 件已成功加载,并且将在 IDA Pro 运行期间保持活动状态。如果一个插件的 init 方法返回此值,那么插件的 run 方法可以在任何时候被调用。
        return idaapi.PLUGIN_KEEP

    def run(self, arg):
        idaapi.msg("My IDA Plugin is running\n")
        form = MyForm()
        form.exec_()

    def term(self):
        idaapi.msg("My IDA Plugin is being unloaded\n")
        if hasattr(self, "ui_hook"):
            self.ui_hook.unhook()

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

3.插件所用到的内容

1.如何判断ida反编译二进制文件的位数。

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

2.如何给ida增加右击选项

首先要注册按钮和增加uihook,当ui被点击的时候就会显示出来操作的按钮。

#注册按钮和hook页面

        menu_actions = (
            idaapi.action_desc_t(ACTION_FUNEND, "find fun end", menu_action_handler_t(ACTION_FUNEND), None,
                                 None, 80),
            idaapi.action_desc_t(ACTION_FILLNOP, "Fill with NOPs", menu_action_handler_t(ACTION_FILLNOP), None, None,
                                 9),
        )

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

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

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

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

        if form_type == idaapi.BWN_DISASM and (ARCH, BITS) in [
                                                               (idaapi.PLFM_ARM, 64), ]:
            idaapi.attach_action_to_popup(form, popup, ACTION_FUNEND, "二进制科学/")
            idaapi.attach_action_to_popup(form, popup, ACTION_FILLNOP, "二进制科学/")

3.ida插件的注入时机

flags = idaapi.PLUGIN_KEEP

4.插件功能

这个需要读者自己看代码如何实现的,这里我不再赘述。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二进制科学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值