1. 项目概述当大语言模型遇上“裸奔”的二进制文件在安全研究领域逆向工程和漏洞挖掘一直是技术门槛极高的“硬核”工作。传统的静态分析工具在面对经过编译、剥离了符号和调试信息的“裸”二进制文件Stripped Binaries时往往显得力不从心。分析师需要像侦探一样在浩如烟海的机器码中寻找蛛丝马迹这个过程耗时耗力且高度依赖个人经验。近年来大语言模型LLM在代码理解和生成方面展现出了惊人的潜力这让我们不禁思考能否让一个AI“助手”来协助我们甚至自主地完成对这类“裸”二进制文件的漏洞推理呢“Veritas”这个项目正是对这一设想的深度探索与实践。它的核心目标非常明确为LLM驱动的智能体Agents建立一个可靠的“地面”Grounding机制使其能够对剥离了符号的二进制文件进行稳定、可信的漏洞推理。简单来说就是给一个擅长“阅读理解”的AI大脑配上专业的“显微镜”和“推理手册”让它学会看懂一堆看似无意义的0和1并从中找出可能的安全漏洞。这不仅仅是简单的工具调用而是构建一个集成了反汇编、中间表示IR生成、程序状态模拟和逻辑推理的完整分析框架。对于安全研究员、逆向工程师甚至是希望提升自动化安全评估能力的企业安全团队而言这项技术意味着将部分繁琐、重复的底层分析工作交给AI从而让人能够更专注于高层次的策略判断和复杂逻辑的破解。2. 核心设计思路构建一个“可靠”的AI二进制分析师要让LLM在二进制漏洞挖掘这个专业领域可靠地工作不能仅仅把它当作一个聊天机器人丢给它一堆反汇编代码然后问“哪里有漏洞”。这种“黑盒”式的提问得到的回答往往是笼统的、基于模式匹配的甚至可能是完全错误的幻觉Hallucination。Veritas的设计思路核心在于“Grounding”——即通过一系列技术手段将LLM的推理过程牢牢“锚定”在二进制程序的具体语义和上下文环境中确保其每一步分析都有据可依。2.1 从“裸二进制”到“可理解语义”的转换管道首先需要解决的是输入问题。一个剥离了符号的二进制文件对LLM来说就像一本用外星语言写成的、没有目录和章节标题的天书。Veritas的第一步是构建一个强大的前端处理管道将二进制文件转化为LLM能够更好理解的、富含语义信息的表示形式。这通常不是单一工具而是一个工具链反汇编与基础块划分使用如Ghidra、IDA Pro的后端引擎或radare2、Binary Ninja的API将二进制代码转换为汇编指令。更重要的是需要恢复函数边界和基本块Basic Block结构。对于剥离的二进制这依赖于启发式算法如基于控制流转移指令call, ret, jmp和函数序言/尾声prologue/epilogue的模式识别。生成中间表示IR纯汇编指令对LLM来说仍然过于底层且冗余。因此需要将其提升到更高级的中间表示如Ghidra的P-Code或LLVM IR。IR抽象掉了特定处理器架构的细节如寄存器名称、指令格式更专注于操作语义例如这个指令是在进行内存加载、算术运算还是条件跳转这极大地降低了LLM的理解难度。提取程序语义图谱这是Grounding的关键。系统会从IR或反汇编代码中自动提取多种程序语义信息构建成结构化的图谱或属性列表作为后续推理的“事实基础”。包括数据流图DFG展示变量或数据如何在指令间传递。控制流图CFG展示程序执行的可能路径。函数调用图Call Graph展示函数之间的调用关系。关键变量与常量识别出可能作为大小、偏移量、循环计数器等的变量。内存操作模式识别出堆分配malloc/new、栈分配局部变量、数组访问、指针运算等模式。注意这个转换过程的准确性直接决定了后续推理的天花板。如果反汇编错误导致CFG破碎或者函数识别错误那么后续所有分析都是建立在错误的基础之上。因此Veritas需要集成多种反汇编引擎并进行结果校验或者采用基于神经网络的反汇编恢复技术来提高鲁棒性。2.2 基于“工具调用”的模块化智能体架构Veritas中的LLM Agent并非一个“通才”而是一个“调度员”和“推理引擎”。它自身并不存储所有二进制分析知识而是被训练来熟练使用一系列专门化的“工具”Tools。这种设计借鉴了AI智能体领域的ReActReasoning Acting范式。其工作流程可以概括为“观察-思考-行动-观察”的循环观察ObservationLLM获得当前的分析上下文这可能包括当前正在分析的函数IR片段、相关的数据/控制流子图、之前工具调用的结果如“发现一个大小为用户输入的缓冲区”。思考ReasoningLLM基于当前上下文和最终目标如“寻找内存破坏漏洞”决定下一步该做什么。它需要生成一个清晰的推理链例如“要判断是否存在缓冲区溢出我需要知道缓冲区的分配大小和拷贝数据的来源大小。我已经看到了缓冲区的分配点现在我需要找到向这个缓冲区写入数据的数据流路径。”行动ActingLLM根据思考结果选择一个合适的工具并生成准确的调用参数。例如调用data_flow_analysis(start_address0x401234, variable_name“dest_buffer”)工具。新一轮观察工具执行完毕将结果如“找到三条可能的数据流路径其中一条源自read函数的返回值”返回给LLM成为新的上下文。这种架构的优势在于可靠性具体的、确定性的分析工作如数据流追踪、污点分析由经过验证的工具完成避免了LLM直接生成错误代码或分析。可解释性整个推理过程被记录为一系列工具调用和中间结果安全研究员可以清晰地审查AI的“思考过程”判断其结论是否合理。可扩展性新的分析能力可以通过添加新的工具来引入而无需重新训练整个大模型。2.3 针对漏洞模式的提示工程与推理约束即使有了工具LLM也需要引导才能进行有效的漏洞推理。Veritas会为不同类型的漏洞设计专门的“推理模板”或“提示词链”Prompt Chains。这些模板将漏洞挖掘的过程形式化为一系列结构化的问题。例如针对栈缓冲区溢出的推理模板可能引导LLM执行以下步骤识别候选函数在调用图中寻找使用strcpy,sprintf,gets等危险函数或其内部存在循环拷贝模式的函数。定位缓冲区对于候选函数使用工具分析其栈帧布局识别出局部数组或缓冲区。追踪数据源对写入该缓冲区的数据源进行污点分析追踪到其源头如函数参数、全局变量、文件读取。判断约束分析数据源是否受到有效的大小限制如之前的if (size dest_size)检查。如果没有限制或限制可被绕过则标记为潜在漏洞。评估可利用性可选结合控制流信息判断溢出是否能覆盖返回地址或函数指针初步评估漏洞严重性。这个过程中提示词会明确要求LLM在每一步都引用具体的代码行、变量名或工具输出结果作为证据从而实现“Grounding”。例如LLM的输出不会是“这里可能有一个溢出”而是“在函数sub_401000的地址0x401045处对栈缓冲区local_10大小为16字节调用了strcpy其源指针arg_0指向用户输入且在该调用前未发现对arg_0长度的检查参考数据流分析报告ID:DFA_001。因此存在栈缓冲区溢出风险。”3. 核心组件与关键技术实现拆解Veritas系统的实现是一个复杂的软件工程它需要将二进制分析、程序语言理论和AI智能体技术深度融合。下面我们来拆解几个核心组件的实现要点。3.1 二进制前端处理与语义提取引擎这是整个系统的数据基石。一个健壮的前端需要处理多种架构x86/x64, ARM, MIPS和编译优化选项的二进制文件。实现要点多引擎适配层不应绑定单一反汇编工具。可以设计一个抽象层为Ghidra、IDA通过IDAPython或SDK、radare2、Binary Ninja等提供统一接口。这样可以根据文件类型和分析需求选择最合适的引擎甚至并行分析以对比结果。函数识别与恢复对于剥离的二进制采用递归下降Recursive Descent与启发式规则相结合的方法。先通过入口点Entry Point和调用指令发现函数起始再结合常见编译器生成的函数序言模式如push ebp; mov ebp, esp; sub esp, XX进行验证和扩展。从汇编到LLVM IR的转换这是技术难点。可以复用现有成熟项目如McSema一个将二进制代码提升到LLVM IR的著名框架。它可以处理复杂的指令和间接跳转但配置和使用有一定门槛。Ghidra的P-Code导出Ghidra的中间语言P-Code语义清晰可以编写脚本将其转换为一种简化的、LLM友好的文本表示。自定义IR如果追求极致的定制化和效率可以考虑设计一套自己的简化IR只包含漏洞分析最关键的语义内存读写、算术运算、控制流、函数调用。属性提取器编写静态分析模块遍历IR或基本块提取预设的属性。例如识别所有调用指令并记录目标地址如果可知或指针变量。识别所有内存写操作并分析其目标地址的计算方式是否是基址偏移。识别循环结构并标记循环计数器。使用轻量级的符号执行或值集分析VSA来推断变量的可能取值范围。实操心得在实际构建中我们发现直接使用原始LLVM IR作为给LLM的输入仍然过于冗长。更好的做法是进行“摘要化”Summarization。例如将一个基本块内连续的、无关的寄存器操作折叠将标准的库函数调用如memcpy直接标记为其语义而不是展开成一大堆加载/存储指令。这能显著减少token消耗并让LLM更专注于关键逻辑。3.2 LLM智能体的训练与工具集成如何让LLM学会使用这些复杂的二进制分析工具实现要点工具封装将每个底层分析功能如数据流分析、控制流可达性分析、值集分析封装成具有明确定义输入输出JSON格式的API。例如一个“追踪数据流”的工具输入是{“start_address”: “0x401230”, “variable_name”: “src”}输出是{“sinks”: [{address: “0x401245”, “type”: “store”, “info”: “写入 buffer[0]”}, …]}。提示词工程与少样本学习我们无法提供海量的“二进制分析”对话数据来微调大模型。因此核心方法是少样本Few-Shot学习和思维链Chain-of-Thought提示。在系统提示词System Prompt中需要清晰地定义智能体的角色“你是一个经验丰富的二进制安全分析专家。”可用工具列表及其详细说明每个工具的名字、功能、输入参数格式、输出示例。输出格式要求强制要求LLM以特定的JSON格式如{“thought”: “…”, “action”: {“name”: “tool_name”, “args”: {...}}}来响应。推理范例提供2-3个完整的、从发现问题到调用工具验证的对话示例。这些示例是教学的关键展示了如何从模糊的怀疑到具体的工具调用。微调可选但有效如果资源允许可以收集一些高质量的“分析会话”数据即人类专家引导系统完成漏洞发现的完整交互记录对基础LLM如CodeLlama, DeepSeek-Coder进行监督微调SFT。这能显著提升模型对工具使用的准确性和遵循指令的能力。一个简化的工具调用示例# LLM的思考过程内部推理可输出用于调试 用户查询分析函数 sub_401000 是否存在漏洞。 当前上下文已反汇编 sub_401000发现内部有 memcpy 调用。 思考memcpy 是潜在的危险函数我需要检查其第三个参数拷贝大小是否可控。首先我需要知道 memcpy 调用点的地址和其参数来源。 行动调用工具 find_function_calls参数为 {“function_name”: “memcpy”, “scope”: “sub_401000”}。系统调用工具后返回工具返回在地址 0x401045 发现对 memcpy 的调用。其参数如下基于反汇编 dest: [ebp-0x10] (局部变量) src: [ebp0x8] (第一个参数) size: eax 寄存器的值LLM新一轮思考size 来源于 eax。我需要追踪 eax 在调用前的数据流。调用工具 data_flow_trace参数为 {“start_address”: “0x401045”, “register”: “eax”, “backward_steps”: 10}。3.3 漏洞推理引擎与验证循环这是智能体的“大脑”核心。它接收前端提取的语义信息在提示词的引导下规划分析路径调用工具并综合结果做出判断。实现要点漏洞模式知识库系统需要内置一个漏洞模式库。这不是简单的CWE列表而是可执行的“检查逻辑”描述。例如模式栈缓冲区溢出触发点strcpy,sprintf,gets, 或自定义的循环拷贝。检查步骤定位缓冲区栈变量。确定其大小通过分配指令或类型推断。追踪写入该缓冲区的数据源。确认数据源大小是否未受限制或限制可被绕过。模式整数溢出导致缓冲区溢出触发点内存分配或数组索引计算中的算术运算乘法、加法。检查步骤定位涉及大小的算术运算如malloc(size * 4)。对运算的操作数进行值集分析或符号执行判断是否存在导致溢出的取值空间如size可能为负数或极大值。假设生成与验证循环推理过程本质上是“假设-验证”的循环。LLM会先根据模式库生成一个初步的漏洞假设如“0x401045处的memcpy可能导致溢出”。然后它会自主规划一系列工具调用来验证这个假设追踪数据流、分析大小约束、检查保护机制如栈Cookie。如果验证通过则假设成立如果工具返回的证据否定了假设则LLM应能放弃该假设或提出新的、更精确的假设如“溢出可能仅在特定输入条件下触发”。证据链生成最终的报告不应只是一个结论。系统需要自动生成完整的证据链将LLM的思考过程、调用的每一个工具、每一个工具的输入输出按照逻辑顺序组织起来形成一份可审计、可复现的分析报告。常见问题与排查问题LLM陷入“死循环”反复调用同一个工具或在不相关的细节上打转。排查这通常是由于提示词中对任务目标和推理步骤的约束不够清晰。需要优化系统提示词加入更明确的“停止条件”和“焦点指引”。例如“你的目标是寻找内存破坏漏洞请优先关注内存写入操作和大小计算无需分析算法逻辑细节。” 也可以实现一个简单的循环检测器当LLM在多次调用中未产生新的、推进分析的证据时主动介入并给予提示或重置上下文。问题工具返回的结果LLM无法正确理解或整合。排查检查工具的返回格式是否足够结构化、清晰。过于复杂或非标准化的JSON会让LLM困惑。确保返回结果中的关键信息如地址、变量名、类型有明确的字段名。可以考虑在返回结果中加入自然语言描述的“摘要”字段帮助LLM快速把握重点。4. 系统工作流程与实操案例模拟让我们通过一个虚构但典型的案例来模拟Veritas系统分析一个剥离二进制文件假设是一个简单的网络服务程序中漏洞的完整工作流程。目标二进制server.bin(x64, stripped, 无符号表)怀疑漏洞类型缓冲区溢出4.1 第一阶段前端处理与初始化加载与反汇编系统调用Ghidra后端加载server.bin。由于文件被剥离Ghidra通过递归下降和模式匹配自动识别出多个函数我们将其命名为sub_xxxx。系统选择入口函数附近的、代码量较大的一个函数sub_401200作为初始分析目标可能对应main或主要处理函数。IR生成与摘要将sub_401200及其直接调用的函数如sub_401000的汇编代码提升为简化的自定义IR。摘要过程会将lea rdi, [rbp-0x20]标记为alloc_local_buffer(‘buf’, 32)。将call sub_401000标记为call read_data(‘buf’, 32)如果通过其他分析推断出sub_401000是读取函数。将mov rax, [rbp-0x18]和add rax, 1等序列折叠为语义更明确的表达式。语义图谱构建分析IR为sub_401200构建初步的CFG和DFG。发现函数内有一个循环循环内存在一个对buf的索引写操作buf[i] …索引i是一个递增的变量。4.2 第二阶段智能体引导的深度分析系统将sub_401200的摘要IR、CFG片段和初始观察“函数内存在对局部缓冲区的循环写操作”作为初始上下文连同系统提示词发送给LLM智能体。交互过程模拟智能体第一轮思考“摘要显示有一个循环写缓冲区buf的操作。我需要检查是否存在缓冲区溢出风险。这需要知道buf的确切大小和循环的边界条件。”行动调用工具get_buffer_info参数{“function”: “sub_401200”, “buffer_name”: “buf”}。工具返回{“type”: “stack”, “base_offset”: “rbp-0x20”, “size_inferred”: 32, “allocation_site”: “0x401210”}。同时调用工具analyze_loop参数{“function”: “sub_401200”, “loop_header_address”: “0x401250”}。工具返回{“induction_variable”: “i”, “initial_value”: 0, “bound_check”: {“condition”: “i input_length”, “comparison_address”: “0x401245”}, “increment”: “i”}。智能体第二轮思考“buf大小为32字节。循环边界条件是i input_length。那么input_length是否可控它是否可能大于32我需要追踪input_length的数据来源。”行动调用工具data_flow_trace参数{“start_address”: “0x401245”, “variable_name”: “input_length”, “direction”: “backward”}。工具返回{“origin”: “function_argument”, “arg_index”: 2, “propagation”: [“mov edx, [rbp0x10]”]}。这表明input_length来自函数的第三个参数。智能体第三轮思考“input_length是外部传入的参数。在函数sub_401200内部我没有看到任何对它的校验比如检查是否32。我需要查看调用sub_401200的上层函数看调用者是否进行了校验。”行动调用工具find_callers参数{“callee”: “sub_401200”}。工具返回{“callers”: [{“caller_function”: “sub_401500”, “call_site”: “0x401550”}]}。随后调用工具analyze_caller_context参数{“caller”: “sub_401500”, “callee”: “sub_401200”, “arg_index”: 2}专门分析传递给sub_401200的第三个参数即input_length在调用者处如何产生。工具返回{“argument_value”: “network_read_length”, “validation_before_call”: “none”}。报告显示该参数直接来自一次网络读取的长度字段且在调用前未与固定值32进行比较。4.3 第三阶段结论生成与报告综合所有工具调用的结果LLM智能体进行最终推理证据链函数sub_401200内部循环向栈缓冲区buf32字节写入数据。循环边界由参数input_length控制。input_length来自上层函数sub_401500的网络读取且调用前无校验。因此如果攻击者构造的网络包中长度字段值大于32将导致循环写入越界造成栈缓冲区溢出。漏洞判定高置信度存在栈缓冲区溢出漏洞。定位漏洞根因在sub_401500的0x401550调用处未校验参数具体溢出发生在sub_401200的循环写操作中。报告输出系统生成一份结构化报告包含上述证据链、涉及的地址、代码片段反汇编以及漏洞利用的初步影响评估可能覆盖返回地址。5. 挑战、局限性与未来演进方向尽管Veritas这样的框架前景广阔但在实际落地中仍面临诸多挑战。主要挑战二进制恢复的准确性这是所有分析的基石。混淆、加壳、反调试技术会严重干扰反汇编和CFG恢复。面对高度混淆的二进制现有自动化方法可能失效仍需人工介入。LLM的推理成本与效率每次工具调用和结果整合都需要与LLM交互对于大型二进制文件分析过程可能非常缓慢且消耗大量计算资源API调用成本。需要设计更高效的推理策略比如先由静态分析工具筛选出高风险区域再交由LLM深度分析。误报与漏报LLM可能产生幻觉错误地关联不相关的代码片段或忽略某些复杂的约束条件。工具链的局限性也会导致漏报。系统需要提供一个可信度评分并允许分析师方便地复核证据链。泛化能力针对特定漏洞模式和编译器模式训练的智能体在面对新的代码模式、新的漏洞类型如逻辑漏洞或新的指令集架构时性能可能下降。实操心得与避坑指南起步宜小不宜大不要一开始就试图分析像libc.so这样庞大复杂的库。从一个有已知漏洞的、小型的CTF挑战二进制程序开始验证整个管道。确保前端能正确恢复其关键结构智能体能发现已知漏洞。工具设计要“傻瓜化”给LLM使用的工具其输入输出接口必须极其清晰、稳定、容错。工具内部可以很复杂但对外暴露的API应该简单得像乐高积木。一个混乱的工具接口会让提示词工程变得极其困难。重视可解释性在开发过程中务必保留并可视化LLM的完整“思维链”和所有工具调用记录。这是调试智能体行为、改进提示词、以及最终让安全专家信任AI结论的关键。混合分析策略Veritas不应完全取代传统静态分析工具如符号执行、污点分析而应与之结合。例如可以用符号执行快速扫描出所有可能的溢出点然后由LLM智能体对这些点进行更深入的上下文理解和误报过滤。未来演进方向多模态理解未来的系统可能不仅处理反汇编代码还能结合二进制文件的段信息、导入表、字符串表等甚至处理部分动态分析产生的轨迹数据形成更立体的程序理解。主动交互与查询智能体可以主动向分析师提出澄清性问题例如“这个外部函数sub_403000的预期行为是什么”分析师回答后智能体能将此知识融入后续分析。自动化漏洞利用生成AEG的集成在可靠地识别出漏洞后下一步是自动生成验证性的漏洞利用Exploit。可以将Veritas与符号执行引擎结合利用其推导出的漏洞条件如input_length 32作为约束自动生成触发漏洞的输入样本。构建一个可靠的、基于LLM的二进制漏洞推理系统是一场马拉松而不是短跑。它需要安全专业知识、程序分析技术和AI工程能力的深度结合。Veritas所代表的“Grounded Agent”范式为我们指明了一条让AI在高度专业化、要求精确性的安全领域中发挥实质性作用的可行路径。这条路充满挑战但每解决一个具体问题我们就在让“AI安全分析师”从科幻走向现实的路上又迈出了坚实的一步。