所以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 
通过函数名称获得地址