ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI智能体在商业二进制漏洞挖掘中的Agentic推理实践

AI智能体在商业二进制漏洞挖掘中的Agentic推理实践 1. 项目概述当AI智能体遇上商业二进制漏洞最近几年安全研究圈子里一个越来越热的话题就是如何让AI更“主动”地去发现和推理软件漏洞。传统的静态分析、模糊测试虽然有效但面对海量的、闭源的商业现货COTS二进制文件时往往显得力不从心。这些二进制文件没有源代码符号表被剥离充满了编译器优化和混淆就像一个黑盒子。而“Agentic Vulnerability Reasoning”这个概念简单来说就是打造一个具备自主推理能力的AI智能体让它像一位经验丰富的安全研究员一样去“理解”这个黑盒子并从中找出可能被利用的安全漏洞。这不仅仅是模式匹配而是结合了程序分析、符号执行、大语言模型LLM推理和启发式搜索的综合性任务。我花了相当一段时间深入这个方向从工具链搭建到推理逻辑设计踩了不少坑也积累了一些实战心得。如果你正在面对庞大的、未知的第三方二进制组件安全评估或者对AI在逆向工程与漏洞挖掘领域的结合点感兴趣那么接下来的内容或许能给你提供一个清晰的路线图。2. 核心思路与技术选型背后的考量2.1 为什么是“Agentic”而不仅仅是“Automated”在自动化漏洞扫描领域我们已经有了很多工具。但“智能体”与“自动化脚本”的核心区别在于状态感知、决策制定和长期规划能力。一个自动化脚本可能按照固定流程反汇编 - 匹配危险函数模式 - 报告。而一个智能体则需要理解当前分析所处的“上下文”比如它刚刚分析完一个函数发现了一个可能的缓冲区但它没有直接的溢出路径智能体会记住这个“线索”在后续分析到某个循环或内存拷贝操作时主动回溯并验证这条路径是否构成真正的漏洞。这种推理能力使得智能体能够处理更复杂的漏洞链例如一个Use-After-Free漏洞可能涉及对象分配、释放、指针滞留和再次使用等多个离散步骤跨越多个函数。智能体需要像侦探一样将这些碎片化的信息串联起来。因此我们的技术栈必须支持这种状态维持和目标导向的搜索。2.2 商业二进制COTS Binaries带来的独特挑战选择COTS二进制作为目标意味着我们放弃了所有源代码层面的便利。所有分析都建立在反汇编和中间表示之上。这带来了几个核心挑战语义恢复困难编译器优化会改变代码结构如内联、循环展开剥离的符号信息让我们失去了函数和变量的有意义名称。智能体首先需要重建部分语义例如通过数据流分析推断变量的可能用途。路径爆炸问题二进制代码的路径数量理论上是无限的尤其是在存在循环和间接跳转时。纯粹的符号执行会迅速陷入困境。智能体需要智能地剪枝优先探索那些更可能包含漏洞的路径例如包含对用户输入进行处理或进行内存操作的路径。环境交互模拟许多漏洞的触发依赖于特定的系统状态如堆布局、文件描述符。智能体需要模拟或推理这些外部环境的影响。基于这些挑战我选择的核心架构思路是以混合执行Concolic Execution为引擎以LLM为推理与规划核心以漏洞知识图谱为上下文记忆。混合执行能在具体执行和符号推理之间取得平衡提供可控的路径探索能力LLM如经过微调的Code Llama或DeepSeek-Coder负责解释反汇编代码的语义、提出假设、并制定下一步分析策略知识图谱则用来存储已发现代码结构、潜在危险点、数据流关系供智能体随时查询和连接。注意LLM的选择至关重要。通用的对话模型如GPT-4在代码理解上表现不错但针对二进制反汇编这种特定、嘈杂的输入格式进行过反汇编/漏洞相关数据微调的模型其准确性和可靠性会高出一个数量级。3. 构建智能体核心系统的实操要点3.1 基础分析引擎的搭建与集成智能体的“手脚”是它的分析引擎。我选择了Angr作为基础框架。它是一个功能强大的二进制分析平台支持从反汇编、控制流图恢复、数据流分析到混合执行的全套功能。它的优点是有活跃的社区和相对清晰的API但缺点是学习曲线陡峭且在大规模二进制上资源消耗较大。第一步是建立二进制加载与初步分析管道。这里不能简单地加载完事需要为后续的智能推理准备高质量的“饲料”。import angr import networkx as nx def load_and_prepare_binary(binary_path): 加载二进制文件并进行关键预处理。 proj angr.Project(binary_path, auto_load_libsFalse) # 避免库函数干扰分析 cfg proj.analyses.CFGFast() # 快速恢复控制流图 # 关键识别入口点与潜在高危函数 entry_point proj.entry # 通过函数签名或常见名称模式即使被剥离angr也能部分恢复识别高危API dangerous_apis [strcpy, gets, sprintf, memcpy, free, system] vuln_candidate_blocks [] for func in cfg.functions.values(): # 简单的基于调用关系的初步筛选 for block in func.blocks: if any(api in str(block.disassembly) for api in dangerous_apis): vuln_candidate_blocks.append(block.addr) # 将此信息结构化存入知识图谱 # 例如记录block.addr处可能调用了memcpy属于函数func.addr return proj, cfg, vuln_candidate_blocks这个预处理阶段的目标是生成一个“热点地图”告诉智能体“这些代码块值得优先关注”。这能有效缩小初始搜索空间。3.2 基于LLM的语义理解与假设生成模块这是智能体的“大脑”。我们不是让LLM直接操作Angr而是让它阅读反汇编代码片段并回答关于代码语义和安全性的问题。为此需要设计一个提示词工程模板。我设计了一个多轮提示结构上下文提供将当前分析的基本块Basic Block或一小段指令的反汇编文本连同其前后一些指令提供上下文以及从知识图谱中查询到的相关信息如“该函数之前被标记为处理网络数据”一起输入给LLM。任务指令明确要求LLM扮演一个二进制安全分析专家完成以下任务语义摘要用自然语言描述这段代码在做什么。变量/寄存器追踪识别哪些寄存器或内存位置持有用户可控的输入。风险假设基于代码模式提出1-3个可能存在漏洞的假设例如“如果rax寄存器指向的用户输入长度超过[rbp-0x10]处的缓冲区大小则可能发生栈溢出”。下一步分析建议建议接下来应该符号化哪个变量或者应该沿着哪条控制流路径继续探索以验证某个假设。import openai # 或使用本地部署的LLM API def query_llm_for_reasoning(disassembly_text, context_from_kb): prompt f 你是一个经验丰富的二进制漏洞分析专家。请分析以下x86-64反汇编代码片段 【代码片段开始】 {disassembly_text} 【代码片段结束】 附加上下文{context_from_kb} 请按以下结构回答 1. 语义摘要 2. 关键输入点用户可控的数据可能在哪里 3. 潜在风险假设请具体说明条件和漏洞类型 4. 建议的下一步分析动作 # 调用LLM API response openai.ChatCompletion.create( modelgpt-4-turbo, # 实际使用中可能选用专门微调的模型 messages[{role: user, content: prompt}], temperature0.1 # 低随机性保证分析稳定性 ) return parse_llm_response(response.choices[0].message.content) def parse_llm_response(response_text): # 解析LLM返回的结构化文本提取出假设和建议 # 这是一个简化的示例实际需要更鲁棒的解析器 lines response_text.split(\n) hypotheses [] next_steps [] # ... 解析逻辑 ... return {hypotheses: hypotheses, next_steps: next_steps}这个模块的输出——特别是“风险假设”和“下一步分析建议”——将直接驱动智能体的后续行动。3.3 状态管理与知识图谱的构建智能体需要记忆。我使用Neo4j图数据库来构建知识图谱。图中的节点可以表示函数、基本块、指令、变量抽象寄存器或内存位置、常量、数据流、控制流边、以及抽象的“漏洞假设”。边表示它们之间的关系如“调用”、“跳转到”、“数据依赖于”、“是……的假设前提”。每当LLM生成一个假设如“地址0x401235处的memcpy可能溢出”智能体就在知识图谱中创建一个“假设”节点并将其与相关的代码节点、数据节点连接起来。当后续的分析如符号执行验证了长度约束可被满足证实或否定了这个假设时就更新该节点的状态。这种图形化的表示方式让智能体能够进行复杂的关联查询例如“找出所有还未被验证的、关于缓冲区溢出的假设并且这些假设的源头函数能够从主入口点到达。” 这直接转化为了下一步探索的目标。4. 智能体推理循环的实现与核心算法4.1 主循环感知 - 规划 - 执行 - 学习智能体的核心工作流是一个循环。我将其实现为一个优先级任务队列驱动的循环。class VulnerabilityReasoningAgent: def __init__(self, proj, cfg): self.proj proj self.cfg cfg self.knowledge_graph Neo4jKnowledgeGraph() self.worklist PriorityQueue() # 任务工作队列优先级高的先处理 self.visited_states set() # 避免重复探索相同状态 def run(self): # 初始化将入口点和高危代码块作为初始任务加入队列 initial_tasks self.generate_initial_tasks() for task in initial_tasks: self.worklist.put(task) while not self.worklist.empty() and not self.should_stop(): current_task self.worklist.get() # 1. 感知获取当前代码状态和上下文 code_context self.extract_code_context(current_task.address) kb_context self.knowledge_graph.query_context(current_task.address) # 2. 规划调用LLM进行推理生成假设和下一步建议 reasoning_result self.llm_reasoning(code_context, kb_context) # 3. 执行根据建议启动符号执行或静态分析进行验证 for action in reasoning_result[next_steps]: if action.type symbolic_explore: new_states, findings self.symbolic_execute(current_task, action.constraints) # 将新发现的状态和路径加入知识图谱 self.update_knowledge_graph(new_states, findings) # 将值得跟进的新状态作为新任务加入工作队列 for state in new_states: if self.is_promising(state) and state not in self.visited_states: self.worklist.put(Task(state, priorityself.calculate_priority(state))) self.visited_states.add(state) elif action.type static_verify: verification_result self.static_dataflow_analysis(action) self.knowledge_graph.update_hypothesis_status(action.hypothesis_id, verification_result) # 4. 学习根据本轮执行结果更新任务优先级计算模型一种简单的强化学习 self.adjust_priority_model(current_task, reasoning_result, findings) def calculate_priority(self, state): 计算任务优先级。这是智能体‘策略’的核心。 priority 0 # 规则1距离已知高危API越近的路径优先级越高 priority self.distance_to_dangerous_api(state) # 规则2路径约束中符号化变量代表用户输入的复杂度适中为佳太简单可能没价值太复杂可能无法求解 priority self.evaluate_constraint_complexity(state) # 规则3该路径在知识图谱中关联的未验证假设数量 priority self.count_linked_hypotheses(state) return priority这个循环的关键在于calculate_priority函数。它定义了智能体的“好奇心”和“风险偏好”。初期我采用了一些启发式规则。更高级的实现可以引入一个奖励模型根据“发现真实漏洞”或“快速排除错误假设”的结果通过强化学习来动态调整这些规则的权重。4.2 混合执行与约束求解的深度结合当LLM建议“符号化验证rax的长度是否可能超过缓冲区大小[rbp-0x20]”时智能体需要启动混合执行。这里不仅仅是启动Angr的PathGroup更需要精细控制。核心技巧动态符号化与约束注入不是从一开始就符号化所有输入而是在执行到LLM指定的关注点时才将某个具体的寄存器或内存地址“提升”为符号变量。这能极大缓解路径爆炸和约束求解器的负担。def symbolic_execute(self, start_state, action): 从某个状态开始根据动作进行符号执行。 action中包含了LLM建议的符号化目标如将RDX符号化和需要验证的假设条件。 # 克隆一个状态进行操作避免污染原状态 simgr self.proj.factory.simulation_manager(start_state) # 在执行到特定地址时插入一个“钩子”来符号化目标 def make_symbolic(state): # 例如根据action将RDX寄存器替换为一个符号值命名为“user_input_len” state.regs.rdx claripy.BVS(user_input_len, 64) # 64位符号变量 # 可以施加一些初始约束比如长度是正数且小于一个很大的数 state.solver.add(state.regs.rdx 0) state.solver.add(state.regs.rdx 0x10000) return state # 假设LLM关注的指令地址是0x401240 self.proj.hook(0x401240, make_symbolic, length0) # 设置探索目标执行到包含潜在漏洞的指令如0x401255的memcpy avoid_addresses [0x401300] # 一些已知的“失败”分支地址如错误处理 simgr.use_technique(angr.exploration_techniques.Explorer(find0x401255, avoidavoid_addresses)) simgr.run() if simgr.found: vulnerable_state simgr.found[0] # 现在验证假设在0x401255处复制的长度rdx是否可能大于目标缓冲区大小假设为0x20 # 缓冲区大小可能来自之前的分析存储在知识图谱中或从当前状态的内存布局推断。 buffer_size 0x20 # 构造漏洞条件 user_input_len buffer_size vulnerability_condition vulnerable_state.regs.rdx buffer_size # 询问求解器在当前路径的所有约束下这个条件是否可能为真 is_vulnerable vulnerable_state.solver.satisfiable(extra_constraints[vulnerability_condition]) if is_vulnerable: # 获取一个具体的触发输入反例 trigger_input vulnerable_state.solver.eval(vulnerable_state.regs.rdx, cast_toint) return [vulnerable_state], {vulnerability_found: True, type: buffer_overflow, trigger: trigger_input, address: 0x401255} else: return [vulnerable_state], {vulnerability_found: False} return [], {}这个流程将LLM的定性假设转化为了可被数学求解器验证的定量约束是“推理”得以落地的关键一步。5. 实战中的挑战、调优与避坑指南5.1 路径爆炸与状态空间管理的实战策略即使有智能引导路径爆炸依然是最大的敌人。以下是我总结的几个有效策略积极的状态合并State MergingAngr支持状态合并。当两条路径在某个点之后其内存和寄存器状态在逻辑上等价时可以合并它们避免后续的指数级分裂。这对于处理循环特别有效。需要在分析精度和性能之间权衡。simgr.use_technique(angr.exploration_techniques.MergePoints())循环限制与摘要Loop Limiting and Summarization对于已知的简单循环如内存初始化可以强制限制其迭代次数如3次或者使用预定义的“摘要”函数来模拟其效果而不是具体执行每一次迭代。优先级驱动的渐进式探索不要试图一次性分析完整个二进制。calculate_priority函数应设计成优先探索“浅”的离入口点近的、与高危操作相关的路径。一旦在某条路径上花费了过多时间如约束求解超时而没有进展应主动降低其优先级或暂停探索。5.2 提高LLM推理准确性的关键技巧LLM在反汇编理解上会“胡言乱语”。提升其准确率是项目成败的关键。提供高质量的上下文不要只给LLM一个基本块。提供该函数开头的指令、控制流图的前驱和后继块信息、以及从数据流分析中得到的简单类型信息如“这个栈位置可能是一个缓冲区指针”。上下文越丰富LLM的猜测就越准。实施链式验证Chain-of-Verification不让LLM一次性输出所有结果。设计多轮对话第一轮请描述这段代码。第二轮基于你的描述请识别所有数据输入点。第三轮针对每个输入点结合代码中的安全操作如边界检查评估风险。 这种分步引导能减少幻觉。建立反汇编指令到高级语义的映射库对于常见的、模式固定的指令序列如函数序言/尾声、简单的循环结构可以预先编写解析器将其转化为标准语义描述如“分配了0x30字节的栈缓冲区”直接提供给LLM而不是原始的汇编代码。这降低了LLM的解析负担。5.3 知识图谱的设计与查询优化知识图谱如果设计不当会变成性能瓶颈。节点与关系的粒度要适中不要为每一条指令都创建节点。以“基本块”或“函数”为基本代码单元节点。数据节点可以细化到“栈偏移-0x20处的4字节整数”。关系类型要预先定义好如CALLS、JUMPS_TO、DATA_DEPENDS_ON、ALIAS_OF、HYPOTHESIZED_FROM。索引是关键为节点的属性如地址、类型、状态和常用的查询模式如“查找所有调用memcpy的函数”建立索引能极大提升查询速度。定期清理与归档对于已经被彻底验证为安全或无关的路径及其相关节点可以将其归档或从活跃图谱中移除保持当前推理上下文的小巧与聚焦。5.4 评估与结果验证的闭环如何判断智能体找到的是真正的漏洞需要一个验证闭环。生成可复现的测试用例当符号执行找到一个潜在漏洞时求解器可以生成具体的输入值。智能体应自动将这些输入值封装成一个最小的测试程序例如一个调用目标函数并传入恶意参数的C程序或者在模拟环境中执行。动态模糊测试验证将智能体发现的“可疑路径”和“生成的输入”作为种子输入给一个传统的覆盖引导模糊测试器如AFL、LibFuzzer。观察模糊测试器是否能快速触发崩溃这能提供非常强的证据。人工审核队列最终所有被智能体标记为“高置信度”的漏洞都需要经过安全研究员的人工审核。智能体应该提供清晰的报告包括漏洞位置、触发路径、数据流分析和生成的PoC。这个审核结果又可以作为反馈用于优化智能体的优先级模型和LLM的提示词。6. 典型问题排查与效能提升技巧在实际部署和运行过程中你会遇到各种各样的问题。下面这个表格整理了一些常见问题及其排查思路和解决方案。问题现象可能原因排查步骤与解决方案LLM频繁输出无关或错误假设1. 提示词不清晰或上下文不足。2. 反汇编代码片段过长或过短缺乏关键信息。3. 使用的LLM模型未针对汇编/二进制分析进行微调。1.优化提示词采用更结构化的指令明确要求输出格式。提供函数签名如恢复的、调用约定等元信息。2.调整代码窗口以基本块为中心前后各包含1-2个块。对于复杂逻辑先提供控制流图描述。3.切换或微调模型尝试Code Llama、StarCoder等代码专用模型或在二进制-描述对数据集上对模型进行轻量级微调LoRA。符号执行速度极慢很快卡住1. 路径爆炸。2. 约束求解器遇到复杂非线性运算。3. 模拟了不必要的库函数或系统调用。1.应用探索技术启用状态合并(MergePoints)设置超时和深度限制。2.简化约束避免符号化浮点数或复杂加密运算。使用“摘要”函数替代具体模拟。3.设置钩子(Hook)用简单的返回成功/固定值的函数替换复杂的库函数如printf,malloc。使用auto_load_libsFalse。知识图谱查询成为性能瓶颈1. 图谱中节点和关系数量过多。2. 查询语句未优化导致全图扫描。3. 数据库连接或网络延迟。1.实施图分区按功能模块或地址范围将大二进制分割成子图分析。2.创建索引为节点属性地址、类型和常用关系CALLS创建索引。3.缓存热点查询将频繁查询的结果如“所有调用memcpy的点”缓存在内存中。智能体在无关代码区“打转”任务优先级函数设计不合理未能有效区分“有希望”和“无希望”的路径。1.丰富优先级信号除了距离高危API的远近加入代码复杂度、循环深度、符号变量数量等因子。2.引入衰减机制对长时间探索未产生新发现的状态逐步降低其优先级。3.设置探索预算为每个主要函数或模块分配固定的“时间/资源预算”超时则暂时搁置。生成的PoC无法在真实环境中触发崩溃1. 环境差异如堆布局、ASLR。2. 符号执行时的约束过于宽松生成的输入逻辑正确但实际内存布局不匹配。3. 漏洞是条件竞争等非确定性类型。1.增强环境模拟在符号执行中更精确地模拟堆管理器和内存分配器。2.收紧约束对指针值施加更多合理性约束如对齐、指向可写内存区域。3.结合动态分析将PoC作为种子输入给模糊测试器在真实或高度仿真的环境中进行压力测试。一个关键的效能提升技巧分层分析策略不要一开始就对整个二进制启动完整的智能体分析。我采用一个三层漏斗策略快速筛选层使用轻量级静态分析如基于Angr的VFG数据流分析和简单的模式匹配快速扫描整个二进制标记出所有“可疑点”如调用危险函数、存在未经验证的用户输入使用。这能生成一个初始的、范围较大的“热点列表”。智能推理层智能体聚焦于上述热点列表所在的函数和路径。这是主循环工作的地方综合运用LLM和符号执行进行深度推理。深度验证层对于智能体标记为“高置信度”的漏洞启动更耗时但更精确的分析如全路径符号执行在有限范围内或导向性模糊测试以生成高质量的PoC。这套方法能确保将有限的计算资源集中在最可能产出成果的区域。构建这样一个用于商业二进制漏洞推理的AI智能体是一个系统工程它融合了程序分析、人工智能和软件安全三个领域的知识。最大的体会是不存在“银弹”。LLM不是全知全能的它更像一个富有想象力但有时会犯迷糊的助手符号执行是强大的引擎但需要精细的操控以防失控。项目的成功很大程度上取决于如何设计好它们之间的交互接口和决策循环。从简单的启发式规则开始逐步迭代引入更多反馈信号来优化智能体的“直觉”这个过程本身就像在训练一个特殊领域的安全专家。最终这样一个系统不会完全替代安全研究员但它能成为一个不知疲倦的、可以处理海量单调分析工作的强大助手将人类专家从繁琐的初步筛选中解放出来去专注于更复杂的逻辑推理和漏洞利用链的构建。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进