
平时用 AI 编码客户端写业务代码已经成了习惯可一旦切换到逆向工程场景大部分模型的表现会一下子“降智”。不是模型不懂反汇编也不是它不会读代码而是它缺少一套工程化的处理流程。最近我把自己在安全研究里积累的一套逆向工作流整理成了一个叫 reverse-skill 的项目。简单来说reverse-skill 是一套面向 AI 编码客户端的逆向工程技能路由包它把文件识别、静态反汇编、动态调试、协议还原、密码算法识别这类高频任务封装成独立的 skill再通过一个轻量的路由层让 AI 客户端在收到任务时自动选择并加载正确的技能。它解决的核心问题很直接AI 编码客户端拿到一个未知二进制文件时经常不知道接下来该调用哪个工具、按什么顺序执行、输出什么形态的结果。如果你在做 CTF、闭源 SDK 兼容分析、恶意文件应急或者只是想把一个老程序逆向梳理成文档这个路由包都能帮你把“AI 自由发挥”变成“AI 按流程干活”。1. 技能路由包整体设计与思路拆解1.1 AI 编码客户端在逆向场景中的三个典型痛点先说个我自己的观察。用 Cursor、Claude Code 这类 AI 编码客户端写 CRUD 页面、调接口、补测试体验相当顺滑。但只要丢进去一个 .elf 或 .exe 文件说一句“帮我看下这个程序在干什么”模型十有八九给你一套正确的废话建议你用 file 查看文件类型、用 strings 看字符串、用 objdump 反汇编、用 gdb 调试。这些建议单独看全对但拼在一起根本没法指导一个新手完成逆向分析。第一个痛点是任务缺少操作序列。逆向工程本质上是一个决策链很长的过程拿到文件后先做什么看到某个现象后下一步转向哪里这是一个不断收敛的判断过程。通用模型虽然知道很多工具的用法但它不知道在“现在”这个具体节点应该调用哪个工具。打个比方让一个没拆过机的新手去修手机跟他讲“你要检查屏幕、检查电池、检查主板”是没用的他需要的是“先拧哪颗螺丝、再撬哪个卡扣、用什么工具”的步骤。第二个痛点是上下文被无效信息占满。很多 AI 客户端支持直接把文件拖进对话但二进制文件不是文本扔进去只会变成一堆乱码白白浪费上下文窗口。就算你把文件转成 hex 或反汇编文本一股脑塞进去模型也会被海量数据淹没反而抓不住关键逻辑。我更倾向于让模型“读摘要”“读局部”而不是“读全文”。第三个痛点是工具链没有编排逻辑。一个真实的逆向流程里file 的输出会影响你选择静态分析还是动态调试checksec 的结果会影响你对保护措施的理解strings 里出现的特殊字段可能直接指向某个协议。这些工具的依赖关系和优先级是靠经验累积出来的通用模型没有这种“条件反射”所以它给出的建议经常是跳跃的、不连贯的。其实这三点背后的原因是一致的模型缺少一个结构化的任务框架。reverse-skill 就是为这个框架设计的。1.2 路由包的设计哲学把经验翻译成流程我做 reverse-skill 之前试过很多种方案比如写一份超长的 system prompt把逆向工程的所有原则、工具、步骤全部塞给模型。结果很尴尬prompt 越长模型越容易“盯着原则忘掉任务”而且每次对话都要重新加载几千字规则浪费上下文效果还不稳定。后来我换了个思路不再给模型灌一整套方法论而是把方法论拆成一组可独立调用的 skill按需加载。就像你请了一个顾问团队平时不带在车上遇到什么问题就叫对应领域的顾问过来。每个 skill 负责一个子任务只在这个子任务被触发时才加载这样上下文负担小专业度也高。整个路由包的核心流程是这样的用户提出一个问题router 层对问题进行意图判断匹配到对应的 skill然后把 skill 的内容以提示词、工具定义或外部指令的形式交给 AI 客户端AI 再按照 skill 里定义的步骤执行并输出结构化结果。这中间的“路由”两个字是整个设计的关键。传统做法是“一个巨大的 prompt 管理所有场景”reverse-skill 则是“多个小 skill 一个判断器”。判断器可以很简单我最初的实现只有几十行规则和正则就已经比“万能 prompt”可靠很多。那为什么不直接让模型自己决定下一步呢我在多个模型上试过只跟模型说“你现在是一个逆向工程师”它依然会陷入选择困难。模型不是没有知识而是缺少优先级。路由层就是那个“优先级判断器”它把“人类工程师在这个场景下会先做什么”的经验显式地写出来模型只需要执行。1.3 reverse-skill 与普通 Prompt、Custom Instructions 的差异很多人会问这跟 Cursor 里的 rules、Claude Code 里的 CLAUDE.md或者一个精心编写的自定义指令有什么区别区别在于触发机制和职责边界。普通的 rules 或 instructions 更接近“恒定约束”它们定义一个项目长期遵守的规范比如代码风格、测试要求、目录结构。而 reverse-skill 里的每个 skill 是按场景触发的流程模板同一个项目里可以挂几十个 skill只有被 router 命中的那个才会真正影响模型行为。我做过一个对比同样一句“分析这个 samples/runme 文件”如果用固定提示词模型会给出通用的五步法如果挂载了 reverse-skillrouter 会识别出这是一个 ELF 文件触发 file_triage 这个 skillAI 会先执行 file、strings、checksec并输出一份“文件初筛报告”。后者的结果更接近真实逆向工程师的产出而且整个过程是可复现的——换一个模型来跑只要路由逻辑不变结果就基本一致。还有一个差异是上下文开销。一个 full 的逆向方法论提示词可能有两三千字但一次分析任务实际只会用到其中一小部分。技能路由包按需加载每次只注入一个 skill通常几百字就够剩余上下文全部留给分析数据。这在处理大型二进制时非常重要。2. 核心细节解析与实操要点2.1 定义一个 skill 的标准结构reverse-skill 里的每个 skill 就是一个结构化文档我用 YAML 格式维护因为它可读性好也方便被程序解析。每个 skill 包含以下字段字段作用示例name技能名称全局唯一file_triagedescription技能职责描述供 router 做意图匹配对未知文件进行类型识别、基础信息采集和初步定性triggers触发词、正则模式或意图描述[file, 文件类型, what is this, \\bELF\\b]inputs该技能需要的外部输入目标文件路径、字符串提取范围tools需要调用或建议使用的工具链file,strings,checksec,entsteps可执行步骤清单按顺序排列依次采集文件头信息、提取字符串、计算熵值output_format技能输出的标准模板输出 Markdown 表格字段/值/初步推断pitfalls常见坑位与注意事项不要直接打开超过 10MB 的文件字节序影响熵结果下面是一个真实可用的 skill 示例也是我路由包里第一个写的file_triagename: file_triage description: 对目标文件进行初步识别判断文件类型、架构、链接方式、 保护措施并提取关键字符串与熵值。适用于任何未知名文件的 第一次分析。 triggers: - file - 文件类型 - 这是什么 - what is this - \\b(file|elf|pe|mach-o)\\b inputs: - path: 目标文件的本地路径 tools: - file - strings - checksec - ent steps: - 运行 file path记录文件类型、目标架构、字节序 - 运行 checksec --filepath记录 NX、PIE、RELRO、Canary 状态 - 运行 strings -n 6 path提取长度大于等于 6 的字符串并分类 - 运行 ent path记录文件熵值并判断是否可能加壳或加密 - 综合以上输出生成文件初筛报告 output_format: | ## 文件初筛报告 - 文件类型file 输出 - 架构/字节序... - 保护措施... - 可疑字符串... - 熵值结论... - 初步判断... pitfalls: - 不要在 strings 输出过长时全量粘贴给模型应截取前 100 行 - checksec 对非可执行文件可能报错需在 steps 里加异常分支这个结构看起来简单但实际使用中非常有效。因为它把“人类工程师会怎么做”显式化为模型可执行的步骤模型不再需要自己猜测。2.2 技能划分的粒度控制我第一次做技能路由包时一个常见的误区是技能划分太细或太粗。太细会导致路由复杂、每个 skill 都是碎片太粗又回到“万能 prompt”的老路。经过多轮迭代我最终把逆向工程场景收敛成了八个基础 skillfile_triage文件初筛识别类型和基本信息。static_disassemble静态反汇编定位入口点、函数表和关键交叉引用。decompile_scan反编译与伪代码分析结合 Ghidra 或 retdec 输出还原核心逻辑。dynamic_debug动态调试用 gdb 或 ltrace 追踪运行时行为。protocol_reverse协议分析与抓包日志梳理。crypto_identify密码学算法识别与硬编码密钥搜索。unpack_shell加壳与混淆样本的去壳检测和初步处理。report_writer汇总所有阶段产物生成结构化逆向分析报告。每个 skill 我控制在 300 到 600 字之间。太长了模型执行到后面容易“迷失”太短则无法提供足够信息。你可以在自己项目里按需要增删但建议保持“一个 skill 只负责一条完整任务链”的原则。举个例子crypto_identify不会去管文件初筛它只关心怎么识别 AES 常量、如何搜索密钥、如何通过熵值判断某块数据是否加密。这样路由时非常清晰用户说“这里有一段密文”router 直接命中crypto_identify模型立刻进入算法识别模式不会被其他步骤干扰。2.3 路由匹配怎么让 AI 选到正确技能路由层是整个包的大脑。我在实现里用了三层策略从快到慢依次是关键字硬匹配、正则模式匹配、LLM 意图打分。关键字硬匹配最快适合“When user says file type, answer file_triage”这种明确指令。比如用户输入包含checksec、NX、PIE大概率是静态保护分析。正则模式匹配用于处理一些可变表达比如/\.(elf|so|dll|exe)$/i可以识别路径中的可执行文件后缀。但真实用户提问往往不是标准命令比如“这个程序好像有壳怎么处理”这种自然语言问题关键字和正则都可能失效。这时候我用一个简单的打分函数把 skill 的 triggers 和 description 里的关键词拆分成词袋对用户输入做术语匹配得分最高的 skill 胜出。这个函数不依赖大模型几毫秒就能出结果。如果打分结果有多个 skill 分数接近说明用户的问题本身是复合型的我会采用混合触发模式先加载第一个 skill等它跑完输出再让 router 根据输出决定是否触发下一个 skill。这其实就是一个轻量级的“状态机”思路我用一个state变量记录当前分析阶段不同阶段允许触发不同的 skill。真实的路由代码我放在第三章展示这里先提一个经验router 不要试图理解所有话。它只需要做分类把判断结果交给 skill 之后真正的内容理解模型自己会完成。这样设计router 可以做得非常轻甚至不依赖任何外部 API。3. 从零构建一个可用路由包3.1 目录结构设计我把 reverse-skill 组织成一个纯本地项目没有任何外部依赖时也能跑。目录结构如下reverse-skill/ ├── router.py # 路由主逻辑 ├── skills/ │ ├── file_triage.yaml │ ├── static_disassemble.yaml │ ├── decompile_scan.yaml │ ├── dynamic_debug.yaml │ ├── protocol_reverse.yaml │ ├── crypto_identify.yaml │ ├── unpack_shell.yaml │ └── report_writer.yaml ├── state.json # 当前分析会话的进度状态 └── output/ # 各阶段产物 ├── triage.md ├── disasm/ ├── decompile/ └── final_report.md这个结构很简单核心是skills/目录下的 YAML 文件和router.py。state.json用来记录当前分析进度这样 AI 客户端可以在多轮对话中保持路由状态一致。实际使用时我通常再建一个samples/目录放待分析文件并在.gitignore里忽略它避免把二进制样本误提交到仓库。3.2 路由模块实现我用 Python 实现了一个最小路由模块。整个思路是读取所有 skill 文件解析出 name、description、triggers然后与用户输入做匹配最后返回命中的 skill 内容。#!/usr/bin/env python3 import re import sys import yaml from pathlib import Path SKILLS_DIR Path(__file__).parent / skills def load_skills(): skills [] for path in SKILLS_DIR.glob(*.yaml): with open(path, r, encodingutf-8) as f: data yaml.safe_load(f) data[file] path.name skills.append(data) return skills def score_skill(skill, text): score 0 text_lower text.lower() for trigger in skill.get(triggers, []): if trigger.startswith(\\b) or trigger.startswith((): if re.search(trigger, text_lower): score 3 else: # 普通关键词按出现次数计分 count text_lower.count(trigger.lower()) score min(count, 3) # description 中的术语也能加分 for word in re.findall(r[a-zA-Z_]{3,}, skill.get(description, )): if word.lower() in text_lower: score 1 return score def route(text, skills): scores [] for skill in skills: s score_skill(skill, text) if s 0: scores.append((s, skill)) if not scores: return None scores.sort(keylambda x: x[0], reverseTrue) # 取最高分如果有多个接近返回前两个由上层决定 return scores[0][1] if __name__ __main__: text sys.stdin.read().strip() skills load_skills() hit route(text, skills) if hit: print(fmatched: {hit[name]}) print(---) print(yaml.dump(hit, allow_unicodeTrue, sort_keysFalse)) else: print(no skill matched)这个实现有几个值得注意的设计点。第一score_skill对正则触发词和普通关键词分别处理。像\bELF\b这种正则如果原样当作关键词去 query.lower() 里 count根本不会命中所以要先判断是否以正则特征开头。第二路由结果分数相同时我没有直接抛出异常而是返回分数最高的同时由上层环节决定是否追问用户。继续追问比“猜一个”更稳妥尤其是恶意文件分析场景猜错技能可能导致误判。第三所有 skill 文件在启动时一次性加载到内存。数量少时可以这么做如果扩展到几十个 skill建议改成按需加载或者用索引文件标记每个 skill 的关键触发词。3.3 接入 AI 编码客户端的三种方式reverse-skill 的路由模块本身不是 AI 客户端它需要和编码客户端配合。我在实际项目中试过三种接入方式各有适用场景。第一种将 router 的输出作为上下文注入。适合 Claude Code、Cursor 这类支持项目内指示文件的工具。我在项目里加一个AGENTS.md或者.cursor/rules里面动态写入路由结果。每次用户提问时先跑一次python router.py把命中的 skill 内容贴进对话。这种方式的优点是不需要客户端额外支持缺点是多一步手动操作。第二种通过 MCP 工具暴露。适合自研 Agent 或支持 MCP 的客户端。我把route()函数封装成一个 MCP tool客户端收到“分析这个文件”时自动调用该 tool拿到 skill 内容后继续后续生成。这样路由过程对用户完全透明体验最好。缺点是 MCP 环境配置有一定门槛。第三种作为标准 skill 文件放到客户端的原生技能目录里。现在很多 AI 编码客户端自带 skill 机制比如某些助手支持把 YAML 文件放到.claude/skills/目录模型会自动读取并匹配。我的 YAML 格式基本兼容这类机制这是最省事的接入方式我把skills/目录直接链接过去就能用。三种方式的对比我放在下面接入方式优点缺点适用场景上下文注入零依赖任何客户端可用需要手动跑 router快速验证思路MCP 工具路由透明自动触发配置复杂自研 Agent、重度用户原生 skill 目录最自然无需中间层依赖客户端识别能力支持 skills 机制的客户端如果你刚开始尝试我建议先用第一种方式跑通流程再逐步迁移到 MCP 或者原生 skill 目录。毕竟核心价值在 skill 内容和路由策略不在接入方式。4. 实战复现还原一个 ELF 文件的核心逻辑4.1 实验环境与合规说明这里用一个模拟场景演示整个流程。我准备了一个自己编译的 Linux ELF 文件它有三个功能接收参数、用内置密钥解密一段内存数据、输出解密结果。整体逻辑很简单但数据包被故意混淆过适合演示技能路由包如何一步步拆解。需要特别说明的是所有样本都是我自己编译的或者来自开源的 CTF 题目、已经授权的分析目标。做逆向工程分析尤其是拿 AI 编码客户端辅助合规边界要自己守住。不要拿别人的商业软件去破解授权校验也不要在没有授权的情况下分析第三方程序。4.2 阶段一file_triage 文件初筛我把文件路径丢给 AI 客户端“请分析 samples/runme 这个文件。”router 收到文本后先做关键字匹配。samples/runme没有直接命中任何 skill 名但分析、文件这类词在多个 skill 的 description 里都有分数会很分散。于是 router 进入正则模式发现用户输入末尾是.runme且没有任何后缀判断这是一个未知名文件最合理的起始点是file_triage于是把该 skill 注入对话。模型按 skill 里的 steps 依次执行file samples/runme # 输出ELF 64-bit LSB executable, x86-64, dynamically linked checksec --filesamples/runme # 输出No PIE, No NX, No Canary strings -n 6 samples/runme | head -50 # 输出中出现了 key:, decrypt, usage: runme input 等可读字符串根据这些输出模型生成了第一份结构化报告文件类型ELF 64 位动态链接可执行文件x86-64。保护措施无 PIE、无 NX、无 Canary说明编译时没有启用常见安全加固这可能是一个用于教学或逆向练习的样本。可疑字符串包含decrypt、key:、usage初步判断程序接受一个输入参数并执行某种解密操作。这个过程如果没有路由包模型可能会直接开始反汇编整个文件浪费大量上下文。而file_triage让 AI 先花最少的资源拿到全局画像为下一步提供路由依据。4.3 阶段二static_disassemble 静态反汇编拿到初筛报告后router 判断下一步应该进入static_disassemble。因为报告显示程序是动态链接的而且有明确的字符串提示不需要先动态调试直接做静态分析定位关键函数更高效。该 skill 指导模型执行objdump -d samples/runme | grep -A 30 main: # 定位 main 函数和它的调用序列 nm samples/runme # 查看符号表 readelf -s samples/runme | grep FUNC # 列出所有函数模型从输出中发现主函数在调用一个名叫parse_input的子函数后又调用了位于.rodata段的一个数据地址。它并没有直接在反汇编文本里寻找而是通过符号表和交叉引用锁定了两个候选函数parse_input和decrypt_buffer。这里有一个实操技巧不要让模型一次性读取整个objdump -d的输出。一个 50KB 的二进制反汇编出来可能有几万行全量塞进去会立刻淹没上下文。我通常在 skill 里写明“使用 grep、sed 先过滤出关键函数再逐步展开”这样模型会把反汇编当成一个“可按需查询的数据库”而不是全部读进对话。4.4 阶段三decompile_scan 与 crypto_identify 交叉分析有了关键函数列表后router 进入decompile_scan。模型调用 Ghidra 的头模式分析或者 retdec 反编译只对main、parse_input、decrypt_buffer三个函数生成伪代码。此时模型看到decrypt_buffer里有一段固定的字节数组以及一个明显的异或循环for (int i 0; i len; i) { out[i] in[i] ^ key[i % key_len]; }这在 CTF 和很多二进制分析里非常常见。但普通模型看到异或循环时不一定知道下一步该做什么而路由包已经在crypto_identify这个 skill 里写好了“异或模式的判断方法”和“密钥长度估值技巧”。于是 router 在反编译输出中检测到xor和循环结构自动追加加载crypto_identify。该 skill 指导模型执行ubelt entropy samples/runme # 或者用 python: scipy.stats.entropy同时模型在.rodata段找到了一个长度为 16 字节的字符串key: R3v3rs3_Sk1ll正好与异或循环中的 key 长度匹配。到这里核心解密逻辑基本清晰程序接收输入字符串与内置密钥按字节异或输出结果。4.5 阶段四report_writer 汇总最后router 检测到用户输入“总结”“报告”等意图触发report_writer。这个 skill 的输出模板要求模型把所有阶段产物整理成一份统一的 Markdown 报告包括文件基本信息、静态分析结论、关键函数伪代码、密码学算法识别结果、以及一个可执行的验证步骤用 Python 复现解密逻辑。我把报告的最终结构列一下方便你参考目标概览文件路径、SHA256、文件类型。初步结论程序通过异或解密输入密钥硬编码在二进制中。关键函数逐一说明main、parse_input、decrypt_buffer的作用。命令行验证给出复现脚本和预期输出。缓释措施如果这是一个真实恶意样本应该如何在沙箱里做动态验证。到这里一个完整的“从未知文件到核心逻辑还原”的流程就结束了。整个过程里AI 客户端始终扮演执行者的角色而在每一步决策上做引导的是 reverse-skill 路由包。5. 常见问题与排查技巧实录5.1 路由选错技能怎么办最常见的情况是用户提问太模糊比如“这个文件很可疑”。router 里多个 skill 的 description 都可能包含“可疑”这类词分数接近导致选错。我的排查思路是先看 router 的原始打分是哪个触发词让分数持平然后在对应 skill 里增加更精确的排除词或触发词。比如在file_triage的 triggers 里增加未知文件、是什么文件在dynamic_debug里增加运行后、调试这样即使输入包含“可疑”也会根据上下文意图选择不同路径。如果模糊程度实在太高我会在 route 函数里增加一个返回阈值最高分低于设定阈值时返回一个“无法确定请补充信息”的默认响应而不是强行选一个。5.2 上下文仍然被撑爆即使按需加载 skill反汇编、反编译输出还是会很快填满上下文。我在static_disassemble和decompile_scan两个 skill 里都加了一条硬性规则任何单次工具输出超过 200 行时必须截断保存到本地文件只把摘要或前若干行发给模型。具体做法是让模型把完整输出写到output/目录下的临时文件然后在步骤里用grep、head、tail快速定位。比如“只查看 main 函数附近的 30 行”比“把整个反汇编看完”高效得多。这个办法很土但极其有效。它本质上是把“外部记忆”从模型上下文转移到了项目文件系统里AI 编码客户端最擅长处理文件就让文件来承担记忆职责。5.3 工具缺失或版本不一致不是所有人都装了 Ghidra、checksec、ent 这些工具。我在 skill 文件的tools字段里标注了每项工具的替代方案比如没有checksec时可以用readelf -h、readelf -l手查 NX、PIE没有ent时写一个 10 行的 Python 脚本来计算熵值。实践中模型通常能处理这种替换但你需要提前把“替代路径”写在 pitfalls 里否则模型会卡在“我没有 checksec”而中断流程。5.4 模型输出格式不稳定路由包的一个核心价值就是稳定输出格式但实际使用中不同模型对 YAML 输出模板的遵循程度不一样。我的解决方式是在output_format里规定“必须用 Markdown 表格列名固定为 字段/值/初步推断”并且在 skill 的开头加一句“严格按模板输出不要额外解释”。如果模型还是跑偏就在 router 与客户端的中间层做一次格式校验检测输出的关键标题是否存在缺失时提示模型重新生成。这个我一般放在 MCP 工具里做因为原生 skill 目录不支持这种后处理。5.5 排错速查表问题现象排查方向解决办法路由误判用户问“文件类型”却触发了dynamic_debug查看 router 触发词打分增加关键词和排除词降低模糊命中率上下文溢出反汇编输出几万行模型开始答非所问检查 skill 的 steps 是否限制输出强制截断、grep 关键函数、写临时文件工具缺失模型说无法执行 Ghidra检查 tools 字段替代方案在 pitfalls 中补充 Python/objdump 替代格式混乱输出报告缺失关键章节检查 output_format 是否被覆盖增加后处理校验重建缺失部分多技能冲突一个文件分析被反复路由到不同 skill检查 state.json 是否正确记录阶段明确 state 切换条件写在最后做了几轮迭代之后我有一个很深的体会reverse-skill 本质上不是在教 AI 逆向工程而是在把经验结构化。真正的价值不在代码量也不在路由算法多么高级而在于把“我遇到这种情况会怎么选”的判断沉淀成了一组可以被模型复用的显式规则。我个人实际跑下来的感受是用路由包之后分析一个样本的平均时间从原来的人工查找约四十分钟缩短到十分钟内重点是输出结果稳定不会因为换了一个模型就出现风格和质量的大幅波动。如果你也想尝试我的建议是不要一上来就写满八个 skill先找一个你最高频的动作比如“文件初筛”把它写成一个 skill用三个样本验证再继续扩展。最后再分享一个小技巧。每个 skill 你在真实跑完一次之后一定要回头修改 pitfalls 字段把那一次踩到的坑补进去。几次迭代之后这个 skill 就会越来越像一个真实的逆向工程师手册而不是一个通用的提示词模板。这就是 reverse-skill 后续最值得扩展的地方——技能包的内容会随你的实战经验一起成长。