VulFi(漏洞查找器)工具是 IDA Pro 的一个插件,可用于辅助在二进制文件中进行漏洞挖掘。其主要目标是在一个视图中提供所有对最感兴趣函数(如 strcpy、sprintf、system 等)的交叉引用。在可以使用 Hexrays 反编译器的情况下,它将尝试排除那些从漏洞研究角度来看并不有趣的函数调用(例如 strcpy(dst,"Hello World!") 这样的调用)。如果没有反编译器,规则会简单得多(不依赖于架构),因此只会排除最明显的情况。
有关更新的更多信息,请参阅 更新日志。
将 vulfi.py、vulfi_prototypes.json 和 vulfi_rules.json 文件放入 IDA 插件文件夹(cp vulfi* <IDA_PLUGIN_FOLDER>)。
在运行 VulFi 之前,请确保您对正在处理的二进制文件有良好的理解。尝试识别所有标准函数(strcpy、memcpy 等)并相应地命名它们。插件不区分大小写,因此 MEMCPY、Memcpy 和 memcpy 都是有效名称。但请注意,对函数的搜索需要精确匹配。这意味着 memcpy? 或 std_memcpy(或任何其他变体)将不会被检测为标准函数,因此在查找潜在漏洞时将不会被考虑。如果您正在处理一个未知的二进制文件,您需要首先设置编译器选项 选项 > 编译器。之后,VulFi 将尽力过滤所有明显的误报(例如使用常量字符串作为第一个参数调用 printf)。请注意,尽管该插件设计为不依赖特定架构,但某些处理器不完全支持指定类型,在这种情况下,VulFi 将简单地将所有对可能危险的标准函数的交叉引用标记出来,以便您进行手动分析。在这些情况下,您可以利用插件的跟踪功能。
要启动扫描,请从顶部菜单栏选择 搜索 > VulFi 选项。这将启动一次新扫描,或者读取之前存储在 idb/i64 文件中的结果。每当您保存数据库时,数据会自动保存。
扫描完成或加载了之前的结果后,将显示一个包含以下列的表格视图:
未检查。其他状态为 误报、可疑 和 易受攻击。这些状态可以通过右键单击某个项目来设置,并应反映对该函数调用进行手动审查的结果。高、中 和 低(对于扫描器无法正确识别所有参数的情况,还有 信息)。优先级与 vulfi_rules.json 文件中的其他规则一起定义。如果 idb/i64 文件中没有数据,或者用户决定执行新扫描,插件将询问是使用默认包含的规则运行扫描,还是使用自定义规则文件。请注意,如果已存在数据,则运行新扫描不会覆盖之前找到的、由与先前存储结果相同名称的规则标识的项目。因此,重新运行扫描不会删除现有的注释和状态更新。

在 VulFi 视图的右键单击上下文菜单中,您还可以从结果中删除该项目或删除所有项目。请注意,执行此操作后,任何注释或状态更新都将丢失。
从版本 2.1 开始,VulFi 还支持同时对多个选定项目执行操作。这允许将多个项目标记为某种状态、删除,或向多行添加相同的注释。有时操作可能不会正确刷新。发生这种情况时,请多次刷新 UI(Ctrl+U),直到看到更改生效。
每当您想要检查检测到的可能存在漏洞的函数实例时,只需双击所需行中的任意位置,IDA 就会将您带到被识别为潜在感兴趣的内存位置。使用右键单击并选择 设置 Vulfi 注释 允许您为该实例输入注释(例如,为状态提供理由)。
该插件还允许创建自定义规则。这些规则可以在 IDA 界面中定义(适合单个函数),或者作为自定义规则文件提供(适合旨在覆盖多个函数的规则)。
当您想在分析期间跟踪自定义函数时,请右键单击其主体内的任意位置,然后选择 将 <name> 函数添加到 VulFi。您也可以在当前反汇编/反编译器视图中高亮并右键单击函数名称,以避免切换到函数主体。

也可以加载包含多组规则的自定义文件。要创建具有以下结构的自定义规则文件,您可以使用此处包含的模板文件 rule_template_file.json。
[ // 规则数组
{
"name": "RULE NAME", // 规则的名称
"function_names":[
"function_name_to_look_for" // 应匹配此规则中定义条件的所有函数名称列表
],
"wrappers":true, // 是否也查找上述函数的包装器(注意包装的函数也必须匹配该规则)
"mark_if":{
"High":"True", // 如果计算结果为 True,则标记为高优先级(请参阅下面的规则)
"Medium":"False", // 如果计算结果为 True,则标记为中优先级(请参阅下面的规则)
"Low": "False" // 如果计算结果为 True,则标记为低优先级(请参阅下面的规则)
}
}
]
下面是一个示例规则,它查找对函数 malloc 的所有交叉引用,并检查其参数是否非常量,以及函数的返回值是否被检查:
{
"name": "Possible Null Pointer Dereference",
"function_names":[
"malloc"
],
"wrappers":false,
"mark_if":{
"High":"not param[0].is_constant() and not function_call.return_value_checked()",
"Medium":"False",
"Low": "False"
}
}
Loop Check - 标记所有循环。在这种情况下,param[0] 是循环的计数器(如果检测到),param[1] 是用于终止循环的检查条件。Array Access - 标记所有数组访问。在这种情况下,param[0] 是数组,param[1] 是索引。创建规则时可以使用与普通函数参数相同的函数(任何可以在 param[<index>] 上调用的内容)。
param[<index>]:用于访问函数调用的参数(索引从 0 开始)function_call:用于访问函数调用事件param_count:保存传递给函数的参数数量param[<index>].is_constant()param[<index>].number_value()param[<index>].string_value()param[<index>].set_to_null_after_call()function_call.return_value_checked(<constant_to_check>)param[<index>].used_in_call_<before|after>(["function1","function2"])function_call.reachable_from("<function_name>")param[<index>].is_sign_compared()param[<index>].used_as_index()param[2].number_value() > 5"%s" in param[1].string_value()not param[1].is_constant()function_call.return_value_checked(param_count)function_call.return_value_checked()all(not p.is_constant() for p in param[2:])any(p.is_constant() for p in param)Truestrlen 检查:not param[1].is_constant() and not param[1].used_in_call_before(["strlen"])read 函数到达:低 优先级。这是为了避免遗漏无法正确获取所有参数的交叉引用(这主要适用于反汇编模式)。Ctrl+U 快捷键手动刷新视图(多次)。function_call.reachable_from("read")