应用安全 --- IDA Pro 函数头批量导出
使用ida内置的识别方法导出
""" IDA Pro Hex-Rays Function Header Exporter 功能:批量导出所有函数的Hex-Rays反编译函数头 """ import idaapi import idc import idautils import ida_funcs import ida_hexrays import ida_name import ida_typeinf from datetime import datetime class HexRaysHeaderExporter(idaapi.plugin_t): flags = idaapi.PLUGIN_UNL comment = "导出Hex-Rays函数头" help = "批量导出所有函数的反编译函数头" wanted_name = "Hex-Rays Header Exporter" wanted_hotkey = "Ctrl-Shift-H" def init(self): # 检查Hex-Rays是否可用 if not ida_hexrays.init_hexrays_plugin(): idaapi.msg("[!] Hex-Rays decompiler 未安装或不可用\n") return idaapi.PLUGIN_SKIP idaapi.msg("Hex-Rays Header Exporter 已加载\n") idaapi.msg("按 Ctrl+Shift+H 导出函数头\n") return idaapi.PLUGIN_OK def run(self, arg): exporter = HeaderExporterCore() exporter.export_all_headers() def term(self): pass class HeaderExporterCore: def __init__(self): self.headers = [] self.failed = [] def check_hexrays(self): """检查Hex-Rays是否可用""" if not ida_hexrays.init_hexrays_plugin(): idaapi.warning("Hex-Rays decompiler 不可用!\n请确保已安装对应架构的反编译器。") return False return True def get_function_header(self, func_ea): """获取函数的反编译头""" try: # 尝试反编译 cfunc = ida_hexrays.decompile(func_ea) if not cfunc: return None # 方法1: 从伪代码中提取第一行(最准确) sv = cfunc.get_pseudocode() if sv and len(sv) > 0: # 第一行通常是函数声明 first_line = ida_lines.tag_remove(sv[0].line) # 清理多余空格和换行 first_line = ' '.join(first_line.split()) return first_line # 方法2: 从类型信息构造 func_type = cfunc.type if func_type: # 获取函数名 func_name = ida_name.get_name(func_ea) # 生成函数声明 declaration = ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, func_type, func_name, '') return declaration return None except Exception as e: return None def get_function_header_simple(self, func_ea): """备用方法:使用类型信息生成函数头""" try: func_name = ida_name.get_name(func_ea) # 获取函数类型 tif = ida_typeinf.tinfo_t() if not ida_typeinf.guess_tinfo(tif, func_ea): return None # 打印类型 result = ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, tif, func_name, '') if result: return result return None except: return None def export_all_headers(self): """导出所有函数头""" if not self.check_hexrays(): return # 询问导出选项 choice = idaapi.ask_yn(1, "选择导出模式:\n\n" "YES - 仅导出成功反编译的函数\n" "NO - 同时导出失败信息\n" "CANCEL - 取消") if choice == -1: return include_failed = (choice == 0) # 选择保存路径 filepath = idaapi.ask_file(1, "*.txt", "保存函数头") if not filepath: return idaapi.msg("\n[*] 开始导出函数头...\n") idaapi.show_wait_box("正在导出函数头...") self.headers = [] self.failed = [] # 获取所有函数 funcs = list(idautils.Functions()) total = len(funcs) for idx, func_ea in enumerate(funcs): if idx % 50 == 0: idaapi.replace_wait_box(f"进度: {idx}/{total}") idaapi.msg(f"[*] 进度: {idx}/{total}\n") func_name = ida_name.get_name(func_ea) # 尝试获取函数头 header = self.get_function_header(func_ea) if header: self.headers.append({ 'address': func_ea, 'name': func_name, 'header': header }) else: # 尝试备用方法 header = self.get_function_header_simple(func_ea) if header: self.headers.append({ 'address': func_ea, 'name': func_name, 'header': header }) else: self.failed.append({ 'address': func_ea, 'name': func_name }) idaapi.hide_wait_box() # 写入文件 self.write_to_file(filepath, include_failed) # 显示结果 msg = f"导出完成!\n\n" msg += f"成功: {len(self.headers)} 个函数\n" msg += f"失败: {len(self.failed)} 个函数\n" msg += f"文件: {filepath}" idaapi.info(msg) idaapi.msg(f"\n[+] {msg}\n") def write_to_file(self, filepath, include_failed): """写入文件""" with open(filepath, 'w', encoding='utf-8') as f: # 写入文件头 f.write("=" * 80 + "\n") f.write("IDA Pro Hex-Rays 函数头导出\n") f.write(f"导出时间: {datetime.now()}\n") f.write(f"二进制文件: {idaapi.get_input_file_path()}\n") f.write(f"成功导出: {len(self.headers)} 个函数\n") f.write(f"失败: {len(self.failed)} 个函数\n") f.write("=" * 80 + "\n\n") # 写入成功的函数头 f.write("=" * 80 + "\n") f.write("函数声明列表\n") f.write("=" * 80 + "\n\n") for item in self.headers: # 格式: 地址 | 函数头 f.write(f"// Address: 0x{item['address']:X}\n") f.write(f"{item['header']}\n\n") # 如果需要,写入失败的函数 if include_failed and self.failed: f.write("\n" + "=" * 80 + "\n") f.write("反编译失败的函数\n") f.write("=" * 80 + "\n\n") for item in self.failed: f.write(f"0x{item['address']:X} - {item['name']}\n") def PLUGIN_ENTRY(): return HexRaysHeaderExporter() # 独立运行 if __name__ == "__main__": exporter = HeaderExporterCore() exporter.export_all_headers()
自己实现的函数参数头导出
""" IDA Pro Function Parameter Analyzer (兼容版本) 修复了 get_func_flags 的兼容性问题 """ import idaapi import idc import idautils import ida_funcs import ida_typeinf import ida_name import json import csv from datetime import datetime class FunctionParamAnalyzer(idaapi.plugin_t): flags = idaapi.PLUGIN_UNL comment = "批量分析函数参数并导出" help = "分析所有函数的参数个数和类型" wanted_name = "Function Parameter Analyzer" wanted_hotkey = "Ctrl-Shift-P" def init(self): idaapi.msg("Function Parameter Analyzer 已加载\n") idaapi.msg("按 Ctrl+Shift+P 启动分析\n") return idaapi.PLUGIN_OK def run(self, arg): analyzer = ParamAnalyzerCore() analyzer.show_menu() def term(self): pass class ParamAnalyzerCore: def __init__(self): self.functions_data = [] def show_menu(self): """显示操作菜单""" choice = idaapi.ask_yn(1, "函数参数分析器\n\n" "YES - 导出为JSON\n" "NO - 导出为CSV\n" "CANCEL - 导出为TXT") if choice == -1: export_type = "txt" elif choice == 0: export_type = "csv" else: export_type = "json" self.analyze_all_functions() self.export_results(export_type) def analyze_all_functions(self): """分析所有函数""" idaapi.msg("\n[*] 开始分析函数参数...\n") self.functions_data = [] func_count = 0 for func_ea in idautils.Functions(): func_count += 1 current = 0 for func_ea in idautils.Functions(): current += 1 if current % 100 == 0: idaapi.msg(f"[*] 进度: {current}/{func_count}\n") func_info = self.analyze_function(func_ea) if func_info: self.functions_data.append(func_info) idaapi.msg(f"[+] 分析完成!共分析 {len(self.functions_data)} 个函数\n") def get_function_flags(self, func_ea, func_obj): """获取函数标志(兼容多版本)""" try: # 方法1: 通过func对象 if func_obj and hasattr(func_obj, 'flags'): return func_obj.flags except: pass try: # 方法2: 使用idc flags = idc.get_func_attr(func_ea, idc.FUNCATTR_FLAGS) if flags is not None and flags != idc.BADADDR: return flags except: pass try: # 方法3: 使用ida_funcs (老版本) if hasattr(ida_funcs, 'get_func_flags'): return ida_funcs.get_func_flags(func_ea) except: pass return 0 def is_library_function(self, func_ea, func_obj): """判断是否为库函数(兼容多版本)""" try: flags = self.get_function_flags(func_ea, func_obj) if flags: return (flags & ida_funcs.FUNC_LIB) != 0 except: pass # 备用方法:通过函数名判断 try: func_name = ida_name.get_name(func_ea) # 常见库函数前缀 lib_prefixes = ['_', 'j_', '__', 'std::', 'strcpy', 'strcmp', 'malloc', 'free', 'printf', 'scanf'] for prefix in lib_prefixes: if func_name.startswith(prefix): return True except: pass return False def analyze_function(self, func_ea): """分析单个函数""" try: func_name = ida_name.get_name(func_ea) if not func_name: func_name = f"sub_{func_ea:X}" # 获取函数对象 func = ida_funcs.get_func(func_ea) if not func: return None # 获取函数类型信息 tif = ida_typeinf.tinfo_t() has_tif = ida_typeinf.guess_tinfo(tif, func_ea) func_data = { 'address': f"0x{func_ea:X}", 'name': func_name, 'param_count': 0, 'params': [], 'return_type': 'unknown', 'calling_convention': 'unknown', 'is_library': self.is_library_function(func_ea, func) } # 如果有类型信息 if has_tif and tif.is_func(): func_details = ida_typeinf.func_type_data_t() tif.get_func_details(func_details) # 参数数量 func_data['param_count'] = func_details.size() # 调用约定 cc = func_details.cc cc_map = { ida_typeinf.CM_CC_CDECL: '__cdecl', ida_typeinf.CM_CC_STDCALL: '__stdcall', ida_typeinf.CM_CC_FASTCALL: '__fastcall', ida_typeinf.CM_CC_THISCALL: '__thiscall', } func_data['calling_convention'] = cc_map.get(cc, 'unknown') # 返回类型 ret_type = func_details.rettype func_data['return_type'] = str(ret_type) # 参数详情 for i in range(func_details.size()): param = func_details[i] param_info = { 'index': i, 'name': param.name if param.name else f"arg_{i}", 'type': str(param.type), 'size': param.type.get_size() } func_data['params'].append(param_info) else: # 尝试从栈帧分析参数 func_data['param_count'] = self.estimate_param_count(func_ea) return func_data except Exception as e: # 静默处理错误,避免大量输出 return None def estimate_param_count(self, func_ea): """估算参数数量(当没有类型信息时)""" try: frame = ida_funcs.get_frame(func_ea) if not frame: return 0 param_count = 0 for member in idautils.StructMembers(frame.id): offset, name, size = member if offset > 0 and offset < 0x100: param_count += 1 return param_count except: return 0 def export_results(self, export_type): """导出结果""" if not self.functions_data: idaapi.warning("没有可导出的数据!") return # 选择保存路径 filepath = idaapi.ask_file(1, f"*.{export_type}", f"保存为 {export_type.upper()}") if not filepath: idaapi.msg("[!] 取消导出\n") return try: if export_type == "json": self.export_json(filepath) elif export_type == "csv": self.export_csv(filepath) elif export_type == "txt": self.export_txt(filepath) idaapi.msg(f"[+] 成功导出到: {filepath}\n") idaapi.info(f"分析结果已导出到:\n{filepath}\n\n共 {len(self.functions_data)} 个函数") except Exception as e: idaapi.warning(f"导出失败: {str(e)}") def export_json(self, filepath): """导出为JSON格式""" output = { 'analysis_time': datetime.now().isoformat(), 'binary': idaapi.get_input_file_path(), 'total_functions': len(self.functions_data), 'functions': self.functions_data } with open(filepath, 'w', encoding='utf-8') as f: json.dump(output, f, indent=2, ensure_ascii=False) def export_csv(self, filepath): """导出为CSV格式""" with open(filepath, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(['Address', 'Function Name', 'Param Count', 'Return Type', 'Calling Convention', 'Parameters', 'Is Library']) for func in self.functions_data: params_str = '; '.join([ f"{p['name']}:{p['type']}" for p in func['params'] ]) if func['params'] else 'N/A' writer.writerow([ func['address'], func['name'], func['param_count'], func['return_type'], func['calling_convention'], params_str, func['is_library'] ]) def export_txt(self, filepath): """导出为TXT格式""" with open(filepath, 'w', encoding='utf-8') as f: f.write("=" * 80 + "\n") f.write("函数参数分析报告\n") f.write(f"分析时间: {datetime.now()}\n") f.write(f"二进制文件: {idaapi.get_input_file_path()}\n") f.write(f"函数总数: {len(self.functions_data)}\n") f.write("=" * 80 + "\n\n") for func in self.functions_data: f.write(f"函数: {func['name']}\n") f.write(f" 地址: {func['address']}\n") f.write(f" 参数数量: {func['param_count']}\n") f.write(f" 返回类型: {func['return_type']}\n") f.write(f" 调用约定: {func['calling_convention']}\n") f.write(f" 库函数: {'是' if func['is_library'] else '否'}\n") if func['params']: f.write(" 参数列表:\n") for p in func['params']: f.write(f" [{p['index']}] {p['name']}: {p['type']} (size: {p['size']})\n") else: f.write(" 参数列表: 无类型信息\n") f.write("\n" + "-" * 80 + "\n\n") def PLUGIN_ENTRY(): return FunctionParamAnalyzer() # 独立运行 if __name__ == "__main__": analyzer = ParamAnalyzerCore() analyzer.show_menu()
浙公网安备 33010602011771号