所以IDA的一键漏洞挖掘的插件在哪里下?
IDA Pro插件编写 本篇文章基于IDA Pro9.0
编写。
本文的大致脉络为:先让读者能够快速编写出能够在IDA
中显示的按钮,也就是UI
部分:),随后笔者会给出扩充功能的方法,以及一些常见的API
。
现阶段IDA Pro
的插件有三种方法可以编写,即C++ SDK
、IDAPython
、IDC
。其区别用AI
分析大致如下:
特性
C++ SDK
IDAPython
IDC
性能
最高 (编译型)
中等 (解释型,但可调用本地代码)
较低 (解释型)
开发速度
慢 (需要编译、调试周期长)
快 (解释型,即写即运行)
非常快 (用于简单任务)
学习曲线
陡峭 (需要 C++
和 IDA
内部知识)
平缓 (需要 Python
基础)
非常平缓
API 访问
最全面 (可访问所有核心功能)
非常全面 (绝大多数功能已封装)
受限
库支持
标准 C++
库和第三方库
极其丰富 (Python 生态系统)
几乎没有
跨平台/版本
需要为不同平台和 IDA 版本重新编译
脚本通常可跨平台,对版本有一定兼容性
兼容性最好
UI 开发
强大 (可使用 Qt
或原生 WinAPI
)
强大 (通过 PyQt
/PySide
使用 Qt
)
非常有限
主要用途
核心功能扩展、高性能分析器、商业插件
快速原型、数据分析、日常任务自动化
简单、一次性的脚本命令
可以看到 IDAPython
是一个不错的选项,它在开发难度和功能丰富度上有着不错的平衡 ,在大多数不严格要求性能的场景下都能很好地适用。因此本篇文章会基于IDAPython
来编写。
你可以将IDApython
理解为原生的python
,没有奇怪的语法,只有各种api
。因此只需要编写一个.py
文件,放到IDA Pro
的plugins
目录下即可。
0x00. 基本框架 一个基本的框架如下所示,各个部分都是必须存在的,根据注释替换即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import ida_idaapi import ida_kernwin class VulnHunter (ida_idaapi.plugin_t): flags = ida_idaapi.PLUGIN_MULTI | ida_idaapi.PLUGIN_UNL comment = "漏洞挖掘的好帮手" wanted_name = "VulnHunter" wanted_hotkey = "Shift-V" help = "" def init (self ): ida_kernwin.msg("VulnHunter: Plugin registered Successfully.\n" ) return VulnHunter_Runner() class VulnHunter_Runner (ida_idaapi.plugmod_t): def run (self, arg ): ida_kernwin.msg("VulnHunter: Exec Func\n" )def PLUGIN_ENTRY (): return VulnHunter()
完成后,我们就可以在IDA中使用这个插件。现在它有基本的功能:
在Edit-Plugin
中加载并调用该插件
使用快捷键Shift+V
调用该插件
0x01. 插件的“动作机制” 这个动作机制实际上是笔者取的名字。在我们使用第一步编写插件的功能时,它很明显只适合单步处理 的功能。很简单,因为它只有一个按钮,我们点击这个按钮,执行一些功能,然后这个功能就执行完毕了。
而实际上我们会有一些别的需求,例如说在实际场景中,我们可能需要让插件拥有二级菜单 ,或者说我们希望在IDA
的代码窗口中通过鼠标右键 来唤起一些功能。
那这里我们就可以用到“动作机制”。我们可以将一个功能视为一个动作,随后我们在插件中定义一个二级菜单,它下面包含多个功能,每一个功能就是一个动作。这样一来可以实现一定程度上的模块化。
在二级菜单添加功能 首先,我们可以编写好各个功能模块(也就是动作)。为了作为演示,这里的功能模块都是空的。
作为例子,这里我们编写了两个空的功能模块,如果需要添加功能,只需要在代码中添加即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class ScanAction (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) def activate (self, ctx ): ida_kernwin.msg("VulnHunter: Scanning for vulnerabilities...\n" ) return 1 def update (self, ctx ): return ida_kernwin.AST_ENABLE_ALWAYSclass ConfigAction (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) def activate (self, ctx ): ida_kernwin.msg("VulnHunter: Opening configuration dialog...\n" ) ida_kernwin.warning("Configuration feature is not implemented yet." ) return 1 def update (self, ctx ): return ida_kernwin.AST_ENABLE_ALWAYS
其中,不难注意到我们需要手动实现activate
方法和update
方法。他们的作用分别如下:
activate
方法:用户执行该动作时会调用该函数。随后,返回值为1
表示修改了数据库。
update
方法:表示该按钮是否可用 。可以根据ctx
,来设置update
的返回值。其中我们使用ida_kernwin.AST_ENABLE_FOR_WIDGET
表示该按钮可用;使用ida_kernwin.AST_DISABLE_FOR_WIDGET
表示该按钮不可用。在上面我们编写的例子中,我们使用了enable_always
,表示该功能始终是可用的状态。注意update
方法会频繁被IDA
调用,因此最好里面不要添加耗时的功能 。
随后,我们需要注册这些动作,并将他们放到二级菜单中。
因此,我们回到加载器的init
函数中,先定义动作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 self.actions = [ ida_kernwin.action_desc_t( name="vulnHunter::scanAction" , label='Scan for Vulnerabilities' , handler=ScanAction(), shorcut='Shift-V' , tooltip='Scan Vulns' , icon=139 ), ida_kernwin.action_desc_t( 'vulnHunter::config' , 'config for vulnHunter' , ConfigAction(), 'Shift+B' , '配置设置' , 139 ) ]
随后,注册这些动作:
1 2 3 4 for action in self.actions: if not ida_kernwin.register_action(action): ida_kernwin.msg(f"VulnHunter Plugin: Failed to register action {action.name} . \n" ) ida_kernwin.msg(f"Action {action.name} register successfully.\n" )
将它们添加到二级菜单:
1 2 3 4 5 6 7 8 9 10 menu_path = 'Edit/VulnHunter/' for action in self.actions: ida_kernwin.attach_action_to_menu( menupath=menu_path, name=action.name, flags=ida_kernwin.SETMENU_APP )
因此,之前的插件可以修改为如下代码,现在他将在IDA
的edit
目录下拥有一个二级菜单,里面有两个功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 import ida_idaapi import ida_kernwin class VulnHunter (ida_idaapi.plugin_t): flags = ida_idaapi.PLUGIN_KEEP comment = "漏洞挖掘的好帮手" wanted_name = "VulnHunter" wanted_hotkey = "Shift-V" help = "" def init (self ): ida_kernwin.msg("VulnHunter: Plugin registered Successfully.\n" ) self.actions = [ ida_kernwin.action_desc_t( name="vulnHunter::scanAction" , label='Scan for Vulnerabilities' , handler=ScanAction(), shorcut='Shift-V' , tooltip='Scan Vulns' , icon=139 ), ida_kernwin.action_desc_t( 'vulnHunter::config' , 'config for vulnHunter' , ConfigAction(), 'Shift+B' , '配置设置' , 139 ) ] for action in self.actions: if not ida_kernwin.register_action(action): ida_kernwin.msg(f"VulnHunter Plugin: Failed to register action {action.name} . \n" ) ida_kernwin.msg(f"Action {action.name} register successfully.\n" ) menu_path = 'Edit/VulnHunter/' for action in self.actions: ida_kernwin.attach_action_to_menu( menupath=menu_path, name=action.name, flags=ida_kernwin.SETMENU_APP ) ida_kernwin.msg("VulnHunter Plugin: Initialized Successfully.\n" ) return ida_idaapi.PLUGIN_KEEPclass VulnHunter_Runner (ida_idaapi.plugmod_t): def run (self, arg ): ida_kernwin.msg("VulnHunter: Exec Func\n" )def PLUGIN_ENTRY (): return VulnHunter()class ScanAction (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) def activate (self, ctx ): ida_kernwin.msg("VulnHunter: Scanning for vulnerabilities...\n" ) return 1 def update (self, ctx ): return ida_kernwin.AST_ENABLE_ALWAYSclass ConfigAction (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) def activate (self, ctx ): ida_kernwin.msg("VulnHunter: Opening configuration dialog...\n" ) ida_kernwin.warning("Configuration feature is not implemented yet." ) return 1 def update (self, ctx ): return ida_kernwin.AST_ENABLE_ALWAYS
如下所示:
0x02. 插件的 UI Hook 在上一部分中我们介绍了在二级菜单添加功能,但这些都属于IDA
上方工具栏中的功能。如果我们需要在别的地方添加功能,例如说在反编译的代码中的鼠标右键想唤起功能时,就需要对UI
进行hook
。
我们接下来以鼠标右键添加两个功能为例子,在反编译的代码中以及汇编代码中,可以鼠标右键代码,并唤起二级菜单中的两个功能。
在鼠标右键添加功能 我们先同样定义好两个动作。在这个例子中,我们将action_name
之类的属性写在了动作的类中。
此外,我们上面提到,动作的类中update
方法决定其是否被显示,因此我们会编写update
方法判断其窗口,如果窗口类型是反编译窗口和汇编窗口,则会显示,除此之外则不会显示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class VulnHunterAction (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) self.action_name = "vulnHunter:action1" self.action_label = "VulnHunter: The action1" self.action_shortcut = "Shift-1" def activate (self, ctx ): current_address = ctx.cur_ea ida_kernwin.msg(f"VulnHunter: Executed at address 0x{current_address:x} " ) ida_kernwin.info(f"VulnHunter scan started at 0x{current_address:x} " ) return 1 def update (self, ctx ): """ 我们判断窗口类型,如果其位于反编译窗口和汇编窗口,右键才会使其显示!:) """ widget_type = ctx.widget_type if widget_type == ida_kernwin.BWN_DISASM or widget_type == ida_kernwin.BWN_PSEUDOCODE: return ida_kernwin.AST_ENABLE_FOR_WIDGET else : return ida_kernwin.AST_DISABLE_FOR_WIDGET class VulnHunterAction2 (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) self.action_name = "vulnHunter:action2" self.action_label = "VulnHunter: The action2" self.action_shortcut = "Shift-2" def activate (self, ctx ): current_address = ctx.cur_ea ida_kernwin.msg(f"VulnHunter: Executed at address 0x{current_address:x} \n" ) ida_kernwin.info(f"VulnHunter scan started at 0x{current_address:x} \n" ) return 1 def update (self, ctx ): """ 我们判断窗口类型,如果其位于反编译窗口和汇编窗口,右键才会使其显示!:) """ widget_type = ctx.widget_type if widget_type == ida_kernwin.BWN_DISASM or widget_type == ida_kernwin.BWN_PSEUDOCODE: return ida_kernwin.AST_ENABLE_FOR_WIDGET else : return ida_kernwin.AST_DISABLE_FOR_WIDGET
随后,我们就可以编写UI Hook
的部分,它继承于ida_kernwin.UI_Hooks
类。其中我们可以定义多种显示的地方,这里我们希望在鼠标右键时显示,因此我们可以实现其finish_populating_widget_popup
方法,如此在鼠标右键菜单生成时,就会触发这个hook
。在代码中,我们添加刚刚定义的几个动作,注意使用的是动作的内部名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class RightMouseHook (ida_kernwin.UI_Hooks): def finish_populating_widget_popup (self, widget, popup_handle ): """ 右键菜单弹出时,附加几个动作 widget表示窗口 popup_handle表示正在被构建的右键菜单本身 """ action_names = [ "vulnHunter:action1" , "vulnHunter:action2" ] for action_name in action_names: ida_kernwin.attach_action_to_popup( widget=widget, popup_handle=popup_handle, name=action_name, popuppath='VulnHunter/' )
最后,我们仍然在插件加载器的init
方法中,注册我们的动作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 action1 = VulnHunterAction() action2 = VulnHunterAction2() self.actions = [ ida_kernwin.action_desc_t( name=action1.action_name, label=action1.action_label, handler=action1, shortcut=action1.action_shortcut, tooltip='action1 text' , icon=139 ), ida_kernwin.action_desc_t( name=action2.action_name, label=action2.action_label, handler=action2, shortcut=action2.action_shortcut, tooltip='action2 text' , icon=139 ) ]for action in self.actions: if not ida_kernwin.register_action(action): ida_kernwin.msg(f"VulnHunter: plugin function {action.action_name} register failed.\n" )
并安装好我们编写的UI Hook
:
1 2 3 4 self.ui_hooks = RightMouseHook()if not self.ui_hooks.hook(): ida_kernwin.msg("VulnHunter: The UI Hook install failed. You may not see the functions in the right mouse button.\n" )
此时,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 import ida_idaapi import ida_kernwin class VulnHunter (ida_idaapi.plugin_t): flags = ida_idaapi.PLUGIN_KEEP comment = "漏洞挖掘的好帮手" wanted_name = "VulnHunter" wanted_hotkey = "Shift-V" help = "" def init (self ): ida_kernwin.msg("VulnHunter: Plugin registered Successfully.\n" ) action1 = VulnHunterAction() action2 = VulnHunterAction2() self.actions = [ ida_kernwin.action_desc_t( name=action1.action_name, label=action1.action_label, handler=action1, shortcut=action1.action_shortcut, tooltip='action1 text' , icon=139 ), ida_kernwin.action_desc_t( name=action2.action_name, label=action2.action_label, handler=action2, shortcut=action2.action_shortcut, tooltip='action2 text' , icon=139 ) ] for action in self.actions: if not ida_kernwin.register_action(action): ida_kernwin.msg(f"VulnHunter: plugin function {action.action_name} register failed.\n" ) self.ui_hooks = RightMouseHook() if not self.ui_hooks.hook(): ida_kernwin.msg("VulnHunter: The UI Hook install failed. You may not see the functions in the right mouse button.\n" ) return ida_idaapi.PLUGIN_KEEPclass VulnHunter_Runner (ida_idaapi.plugmod_t): def run (self, arg ): ida_kernwin.msg("VulnHunter: Exec Func\n" ) class VulnHunterAction (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) self.action_name = "vulnHunter:action1" self.action_label = "VulnHunter: The action1" self.action_shortcut = "Shift-1" def activate (self, ctx ): current_address = ctx.cur_ea ida_kernwin.msg(f"VulnHunter: Executed at address 0x{current_address:x} " ) ida_kernwin.info(f"VulnHunter scan started at 0x{current_address:x} " ) return 1 def update (self, ctx ): """ 我们判断窗口类型,如果其位于反编译窗口和汇编窗口,右键才会使其显示!:) """ widget_type = ctx.widget_type if widget_type == ida_kernwin.BWN_DISASM or widget_type == ida_kernwin.BWN_PSEUDOCODE: return ida_kernwin.AST_ENABLE_FOR_WIDGET else : return ida_kernwin.AST_DISABLE_FOR_WIDGET class VulnHunterAction2 (ida_kernwin.action_handler_t): def __init__ (self ): ida_kernwin.action_handler_t.__init__(self) self.action_name = "vulnHunter:action2" self.action_label = "VulnHunter: The action2" self.action_shortcut = "Shift-2" def activate (self, ctx ): current_address = ctx.cur_ea ida_kernwin.msg(f"VulnHunter: Executed at address 0x{current_address:x} \n" ) ida_kernwin.info(f"VulnHunter scan started at 0x{current_address:x} \n" ) return 1 def update (self, ctx ): """ 我们判断窗口类型,如果其位于反编译窗口和汇编窗口,右键才会使其显示!:) """ widget_type = ctx.widget_type if widget_type == ida_kernwin.BWN_DISASM or widget_type == ida_kernwin.BWN_PSEUDOCODE: return ida_kernwin.AST_ENABLE_FOR_WIDGET else : return ida_kernwin.AST_DISABLE_FOR_WIDGET class RightMouseHook (ida_kernwin.UI_Hooks): def finish_populating_widget_popup (self, widget, popup_handle ): """ 右键菜单弹出时,附加几个动作 widget表示窗口 popup_handle表示正在被构建的右键菜单本身 """ action_names = [ "vulnHunter:action1" , "vulnHunter:action2" ] for action_name in action_names: ida_kernwin.attach_action_to_popup( widget=widget, popup_handle=popup_handle, name=action_name, popuppath='VulnHunter/' )def PLUGIN_ENTRY (): return VulnHunter()
如此一来,我们就可以实现在IDA
的反编译窗口和汇编窗口的右键菜单中,看到并调用我们编写的功能:
0x03. 常用功能 ctx: 当前上下文 在上面编写UI
的过程中,我们能在一个动作的activate
和action
中看到这个ctx
,这其实就是上下文。我们能从当前触发的上下文取出一些数据,这里列出如下所示:
属性 (Attribute)
类型 (Type)
描述 (Description)
枚举值 (Enum Values)
ctx.cur_ea
ea_t
(整数)
当前光标所在位置的有效地址 (Effective Address)。
N/A
ctx.widget_type
int
(枚举)
当前触发动作的窗口/部件 (Widget) 的类型。用于判断上下文。
BWN_DISASM
: 反汇编窗口BWN_PSEUDOCODE
: 伪代码窗口BWN_HEXVIEW
: 十六进制窗口BWN_STRUCTS
: 结构体窗口BWN_ENUMS
: 枚举窗口BWN_FUNCS
: 函数列表窗口
ctx.widget
TWidget*
指向当前窗口/部件对象的指针。可用于更高级的窗口操作。
N/A
ctx.cur_sel
(ea_t, ea_t)
元组,表示用户选择的范围 (start, end)
。若无选择,起始地址为 BADADDR
。
N/A
ctx.cur_func
func_t*
指向光标所在位置的函数对象 (func_t
)。如果不在函数中,则为 None
。
N/A
ctx.cur_struc
struc_t*
(在结构体窗口中) 指向当前结构体对象的指针。
N/A
ctx.cur_strmem
member_t*
(在结构体窗口中) 指向当前结构体成员对象的指针。
N/A
ctx.cur_enum
enum_t*
(在枚举窗口中) 指向当前枚举对象的指针。
N/A
Action 状态控制
常量 (Constant)
作用 (Effect)
详细说明 (Detailed Explanation)
AST_ENABLE_ALWAYS
始终启用
强制启用该 Action,无论当前上下文是什么。这是最强的启用状态。
AST_ENABLE_FOR_WIDGET
在当前窗口启用
仅在当前拥有焦点的窗口/部件 (Widget) 中启用该 Action。当焦点切换到其他窗口时,IDA 会重新调用 update
并根据返回值决定新状态。
AST_ENABLE
启用 (一般情况)
与 AST_ENABLE_ALWAYS
类似,但强度稍弱,IDA 在某些情况下可能会根据其他上下文覆盖此状态。通常建议使用 AST_ENABLE_FOR_WIDGET
或 AST_ENABLE_ALWAYS
。
AST_DISABLE_ALWAYS
始终禁用
强制禁用 (置灰) 该 Action,无论当前上下文是什么。
AST_DISABLE_FOR_WIDGET
在当前窗口禁用
仅在当前拥有焦点的窗口中禁用该 Action。这是实现上下文相关菜单项(例如“仅在反汇编窗口可用”)的关键。
AST_DISABLE
禁用 (一般情况)
与 AST_DISABLE_ALWAYS
类似,但为一般禁用状态。
AST_HIDE_ALWAYS
始终隐藏
将 Action 从菜单和工具栏中完全移除,使其不可见。
AST_HIDE_FOR_WIDGET
在当前窗口隐藏
在当前拥有焦点的窗口中隐藏该 Action。例如,可以让某个功能在反汇编窗口可见,但在伪代码窗口中完全消失。
AST_HIDE
隐藏 (一般情况)
与 AST_HIDE_ALWAYS
类似的一般隐藏状态。
AST_CHECKED
勾选状态
这是一个标志位 ,不能单独使用。必须与一个启用状态通过按位或 (`
函数操作
函数名
作用
ida_funcs.get_func
通过函数地址获得函数,返回值为func_t
ida_funcs.get_func_name
通过函数地址获得函数名称
.start_ea
通过func_t
获得函数的起始地址
.end_ea
通过func_t
获得函数的结束地址
idc.get_name_ea_simple
通过函数名称获得地址