给IDA写个small插件

admin 2024年8月5日14:20:11评论28 views字数 10617阅读35分23秒阅读模式
给IDA写个small插件

之前在修改花指令的时候找不到函数尾巴,和对一些数据块需要nop的时候总要一行行的,或者再写个代码去批量nop,后来觉得插件可以实现,分享给大家也可以用下。

给IDA写个small插件

给IDA写个small插件

⊙一.插件功能

⊙二.插件编写逻辑

⊙三.插件所用到的内容

⊙四.总结

 

.插件功能

 

找函数尾巴(思路来源魔宇)

给IDA写个small插件

arm64汇编批量nop

给IDA写个small插件

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

# 引入 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: Initializedn")
return idaapi.PLUGIN_OK

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

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

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

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

 

.插件编写逻辑

 

为了让python具有能够提示ida中的一些api

我们需要把ida目录中的这个目录

给IDA写个small插件

复制到

给IDA写个small插件

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

 

接下来我们要实现功能:

 

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

 

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

 

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

然后插件如下方所示

 

# 引入 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):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
encodingcount = 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__(selfaction):
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):
startend = t0.place(view).toea(), t1.place(view).toea()
size = end - start
print("start %s ,end %s" % (startend))
self.findfuncend(startsize)
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):
startend = t0.place(view).toea(), t1.place(view).toea()
#这个时候说明选中了一段代码
if (start == end):
idaapi.patch_bytes(start, b"x1fx20x03xd5")
print("%s处填充了一条nop" % hex(start))
else:
#arm64代码指令块为四字节
if(end - start) % 4 != 0:
print("请选中4字节对齐的代码")
else:
for i in range(startend4):
idaapi.patch_bytes(i, b"x1fx20x03xd5")
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(selfstart, sz):
self.ins_map = {"sub""add""str""ldr""stp""ldp"}

target = start
stackOplen = sz
print("正在计算funcstart %x 所在的函数尾" % (target))
encodings = [0xC00x030x5F0xD6]  # 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(selfform, 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__(selfparent=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(selfevent):
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 = [0xC00x030x5F0xD6]  # 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(242117)
self.edit_funstar = QtWidgets.QLineEdit(Dialog)
self.edit_funstar.setGeometry(QtCore.QRect(1003011320))
self.edit_funstar.setObjectName("edit_funstar")
self.button_sure = QtWidgets.QPushButton(Dialog)
self.button_sure.setGeometry(QtCore.QRect(90907523))
self.button_sure.setObjectName("button_sure")
self.label = QtWidgets.QLabel(Dialog)
self.label.setGeometry(QtCore.QRect(30306116))
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(Dialog)
self.label_2.setGeometry(QtCore.QRect(10608116))
self.label_2.setObjectName("label_2")
self.edit_stacklen = QtWidgets.QLineEdit(Dialog)
self.edit_stacklen.setGeometry(QtCore.QRect(1006011320))
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,
None80),
idaapi.action_desc_t(ACTION_FILLNOP, "Fill with NOPs", menu_action_handler_t(ACTION_FILLNOP), NoneNone,
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 runningn")
form = MyForm()
form.exec_()

def term(self):
idaapi.msg("My IDA Plugin is being unloadedn")
if hasattr(self"ui_hook"):
self.ui_hook.unhook()

# Unregister actions
for action in self.registered_actions:
idaapi.unregister_action(action)
idaapi.msg("MyPlugin: Initializedn")
return idaapi.PLUGIN_OK

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

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

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

 

三.插件所用到的内容

 

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.插件功能

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

 

四.总结

写插件的时候可以参考下keyptach和lazyida,里面会有很多的思路值得我们学习。

 

原文始发于微信公众号(二进制科学):给IDA写个small插件

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月5日14:20:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   给IDA写个small插件https://cn-sec.com/archives/2011741.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息