之前在修改花指令的时候找不到函数尾巴,和对一些数据块需要nop的时候总要一行行的,或者再写个代码去批量nop,后来觉得插件可以实现,分享给大家也可以用下。
⊙一.插件功能 ⊙二.插件编写逻辑 ⊙三.插件所用到的内容 ⊙四.总结
一.插件功能
找函数尾巴(思路来源魔宇)
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: 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目录中的这个目录
复制到
这个时候在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
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"x1fx20x03xd5")
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"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(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 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插件
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论