GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- 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()

 

posted on 2025-10-15 14:00  GKLBB  阅读(40)  评论(0)    收藏  举报