【免费下载链接】kong
The world's first agentic reverse engineer.
Kong 是一款基于 LLM 的 Agentic 逆向工程框架,自动驱动 Ghidra 分析 stripped 二进制。它的**类型恢复(Type Recovery)**能力尤为出色:LLM 从反编译代码的字段访问模式中"拼出"完整的 struct 结构体定义,并写回 Ghidra,让param_1变成真正可读的结构体指针。
🧩 为什么需要类型恢复?
stripped 二进制丢失了所有类型信息。反编译后的代码里,一个 32 字节的连接对象可能只长这样:
void FUN_00401a30(undefined4 *param_1) { local = *(undefined4 *)(param_1 + 4); // 这是 socket 描述符? ... }参数类型是undefined4 *,每个字段都藏在魔法偏移量里。类型恢复的目标,就是把这些散落的偏移访问还原成一个有名字、有类型的 struct。
LLM 恰好擅长这种模式匹配——这正是 Kong 让它介入的原因。
🔍 LLM 如何发现结构体线索
固定偏移访问是关键证据
Kong 在系统提示中写明了结构体提案规则(见 kong/agent/prompts.py):
当参数是指针、且函数在多个固定偏移处访问它(如
*(param + 0x10)、param->field_0x8)时,应提出结构体布局;至少要看到 2 个不同偏移的字段访问才提案,单个或含糊的访问不算数。
这条规则很关键——它避免了 LLM 对一次性读写的参数"过度发挥"。
结构体提案的输出格式
LLM 被要求只输出一个 JSON 对象,其中的struct_proposals字段承载类型信息(输出格式定义在 kong/agent/prompts.py):
{ "name": "conn_t", "total_size": 32, "fields": [ {"name": "socket_fd", "data_type": "int", "offset": 0, "size": 4}, {"name": "port", "data_type": "uint16_t", "offset": 8, "size": 2} ], "used_by_param": "param_1" }每个提案都会带上"来源函数"和"被哪个参数使用"的元数据,解析逻辑在 kong/agent/analyzer.py。
🧱 多个提案合并成一个类型
一个 struct 往往被多个函数使用,而每个函数只能看到自己碰过的那几个字段。比如函数 A 只读了type和valueint,函数 B 读到了child、next等 6 个字段——两份提案怎么变成一份完整定义?
这就是 kong/agent/type_recovery.py 中StructAccumulator的工作:
- 收集阶段:每个函数分析完,其
struct_proposals被add_proposals()收集进累加器,并打上来源函数地址标签。 - 合并阶段:
unify()按结构体名称分组,同组内按偏移量(offset)对齐字段逐个合并,总大小取各提案的最大值与"最后一个字段结尾"的较大者(type_recovery.py#L68-L138)。
同偏移冲突时选"最佳字段"
同一偏移可能出现多个候选(名字或类型不一致),_pick_best_field()用打分规则裁决(type_recovery.py#L141-L155):
- 类型更具体者优先:
int胜过undefined4; - 名字更有描述性者优先:
socket_fd胜过field_0x0(field、unk、pad等泛型名扣分)。
真实案例:cJSON 场景
测试用例完美演示了这个流程(tests/test_type_recovery.py):函数 A 只"看见" 24 字节、2 个字段的cJSON;函数 B "看见" 64 字节、6 个字段的cJSON。合并后得到一个64 字节的完整结构体,6 个字段全部保留——这正是"部分视图拼出完整类型"的效果。
✍️ 把类型写回 Ghidra
合并完成只是第一步。apply_unified_structs()(type_recovery.py#L158-L197)负责落地:
- 创建 struct:在 Ghidra 的
DataTypeManager中创建/kong分类下的结构体,字段按显式偏移放置,空隙自动留为未定义字节(kong/ghidra/client.py); - 应用到参数:把该结构体(默认作为指针类型)应用到提案记录的函数参数上,并记录受影响函数的地址(kong/ghidra/client.py);
- 签名重试:分析阶段如果 LLM 给出的函数签名引用了当时还不存在的类型(如
cJSON *item),写入会失败并被标记为 pending;Cleanup 阶段创建好类型后,会自动重试这些签名(kong/agent/supervisor.py)。
这个"先提案、后落地、再补偿"的设计,保证了即使类型和签名在时间上错开,最终状态也是一致的。
🌐 全局合成:第二轮结构体恢复
单函数视角之外,Kong 还有 kong/synthesis/semantic.py 中的SemanticSynthesizer:在所有函数分析完成后,发起一次全局 LLM 调用,回顾连接数最多的 50 个函数,执行三项任务——
- 为被多个函数引用的全局变量(
DAT_XXXXXXXX)起有意义的名字; - 当多个函数从同一基址以一致偏移访问字段时,合成新的结构体定义;
- 在全局视野下修正不协调的函数命名。
这一轮能捕获那些单个函数内看不到 2 个偏移访问、但跨函数汇总后才显现的结构体模式。
🗺️ 类型恢复在流水线中的位置
Kong 的完整流水线是:Triage → Analysis → Cleanup → Synthesis → Export(kong/agent/supervisor.py)。类型恢复横跨其中三个阶段:
| 阶段 | 类型恢复动作 |
|---|---|
| Analysis | LLM 分析每个函数,产出struct_proposals,累加器边跑边收集 |
| Cleanup | 统一提案、在 Ghidra 建类型、应用到参数、重试失败签名 |
| Synthesis | 全局视角再合成一轮结构体与全局变量命名 |
过程事件(如CLEANUP_TYPES_UNIFIED:"Unified N struct proposals into M types")会实时推送到 TUI 界面,方便观察:
📂 相关源码速查
- 累加器与合并算法:kong/agent/type_recovery.py
- 提案数据模型与解析:kong/agent/analyzer.py
- 提示词与输出 Schema:kong/agent/prompts.py
- Ghidra 建类型/应用类型:kong/ghidra/client.py
- 流水线编排:kong/agent/supervisor.py
- 全局合成:kong/synthesis/semantic.py
- 单元测试(含 cJSON 合并用例):tests/test_type_recovery.py
小结
Kong 的类型恢复把"从用法推断类型"这件传统上最耗人力的逆向工作拆成了四步:固定偏移证据 → JSON 结构体提案 → 按偏移合并 + 最佳字段裁决 → Ghidra 建类型并补偿签名,再用一次全局合成扫尾。对新手来说,理解"部分视图如何拼出完整结构体"(按名称分组、按偏移合并、取最大大小)是掌握这套机制的核心钥匙。
【免费下载链接】kong
The world's first agentic reverse engineer.
相关推荐
acwj结构体/联合体/枚举完全指南:复合类型声明、成员访问与代码生成实战
acwj结构体/联合体/枚举完全指南:复合类型声明、成员访问与代码生成实战 在 C 语言编译器 acwj (A Compiler Writing Journey
编译器示例工程教程编程语言Steampipe变量类型详解:从字符串到复杂结构体配置
Steampipe变量类型详解:从字符串到复杂结构体配置 Steampipe作为一款支持零ETL(Extract, Transform, Load)数据查询的开
开发工具云原生Diesel 与 PostgreSQL 复合类型(Composite Types)实战:从匿名元组到带名字段的 Rust 结构体
Diesel 与 PostgreSQL 复合类型(Composite Types)实战:从匿名元组到带名字段的 Rust 结构体 PostgreSQL 的复合类
后端数据库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考