首页
社区
课程
招聘
[原创]Agentic IDA Pro - LLM 驱动的逆向分析专家
发表于: 1天前 450

[原创]Agentic IDA Pro - LLM 驱动的逆向分析专家

1天前
450

项目地址:github.com/magiclf-ai/agentic-ida-pro
版本:IDA Pro 9.3
核心理念:LLM 主导策略决策,工具执行具体动作,全流程可追溯、可验证、可审计


Agentic IDA Pro 核心思想是:

  • LLM 主导策略
  • 工具执行动作
  • 任务板跟踪状态
  • 证据链驱动结论
  • 前后快照做验收

二、为什么是 Agent,而不是更多脚本

逆向分析的典型路径是:

1
入口定位 -> 调用链展开 -> 函数语义分析 -> 数据结构恢复 -> 类型传播 -> 验证

这个路径的关键难点是:它不是固定流程图,而是"观察-决策-执行-再观察"的循环过程

脚本擅长执行固定步骤,但不擅长在证据变化时动态改计划。而 LLM 正好擅长"弱结构决策"——也就是在不完全信息下做下一步规划。

所以我们把角色拆开:

层级 负责方 职责
决策层 LLM 观察证据、规划下一步、选择工具
执行层 Tool 做确定性动作(搜索、反编译、创建结构体)
状态层 Task Board / Knowledge 沉淀结论、管理进度
审计层 Observability / Acceptance 记录日志、验收结果

这就是 Agentic 化真正的价值:不是"替代分析师",而是把分析师的方法论做成能复盘、能审计、能扩展的系统


三、总体架构:主控 Agent + 子 Agent + IDA 服务

先看主路径:

1
2
3
4
5
6
7
8
9
10
11
用户请求
  
ReverseExpertAgentCore(主策略循环)
  
Tool Layer(search/xref/decompile/create_structure/...)
  
IDAClient(HTTP 通信层)
  
ida_service.daemon(IDA 进程内串行执行)
  
IDB 变更 + 反编译结果

再看横向能力:

组件 作用
任务与知识 TaskBoard + WorkingKnowledge,管理进度与沉淀认知
子任务并行 spawn_subagent 异步派生,结果自动回流
上下文压缩 prune_context_messages + compress_context_8block
运行验收 备份、快照、结构体 diff、失败判定
可观测性 会话、turn、tool、事件入库 SQLite,可视化回放

从工程角度看,这个架构有两个关键取舍:

  1. Python 代码保持"薄控制层",避免把业务逻辑硬编码进 if/else
  2. 把策略约束写进 prompt + tool docstring,让 Agent 行为可进化

四、多 Agent 分工:不是"多模型炫技",而是角色隔离

很多多 Agent 系统失败,原因是"每个 Agent 都在做所有事"。我们反过来做:每个 Agent 只做自己最擅长、最可验证的一段

1)主 Agent:ReverseExpertAgentCore

职责:

  • 维护主循环(观察 -> 规划 -> 调工具 -> 更新状态)
  • 管理任务板和知识库
  • 决定是否派生子 Agent
  • 最终调用 submit_output 完成收敛

约束:

  • 关键任务未关闭,禁止 submit_output
  • 有运行中 subagent,禁止 submit_output
  • 每轮结论必须引用工具证据

2)DocumentAgent

检索本地 artifacts 中的可复用模板与历史片段,为主 Agent 提供"路径 + 片段 + 使用建议"。

3)Evidence Reviewer

审核证据链是否闭环,识别"字段命名看起来对了但证据不足"的风险点,给出最小补证动作。

4)Reverse Planner

给主 Agent 输出"下一轮唯一关键动作",把动作描述成可执行、可验证、可回退的形式。

5)Context Pruner

对历史消息做高密度压缩,保留可执行记忆,删除噪音上下文。

6)IDAPython Coder / Executor

  • Coder 负责写出最小修改脚本
  • Executor 负责"执行-报错-修复-再执行"的闭环

这两者共同构成了我们最有特色的一段:CodeAct 式执行环


五、实战演示:一次完整的结构体恢复流程

下面是一个真实的结构体恢复任务执行日志。我们将逐轮剖析 Agent 的决策过程、工具调用和验证闭环。

任务目标

分析关键函数并恢复结构体定义,给出证据链

这是一个典型的逆向分析需求:不仅要找出程序的核心逻辑,还要把散落在代码中的数据结构还原出来,并且每个结论都要有迹可循。


第一轮:任务规划与入口定位

Agent 首先创建任务列表,把大目标拆成可追踪的子任务:

1
2
3
4
5
6
7
8
9
[Tool Calls]
- create_task: {
    "tasks": [
      {"title": "定位关键函数入口/热点函数", "priority": "high"},
      {"title": "采集关键函数伪代码与符号使用证据", "priority": "high"},
      {"title": "创建/迭代结构体定义并应用类型验证", "priority": "high"},
      {"title": "形成证据链报告", "priority": "normal"}
    ]
  }

为什么要先创建任务板?

在长时间的分析过程中,LLM 容易"迷失方向"——忘记自己已经做了什么、还需要做什么。任务板把这些状态外化,每完成一个子任务就标记为 done,形成清晰的可审计路径。

任务创建成功后,Agent 开始执行第一个任务:定位入口函数。

1
2
3
4
5
[Tool Calls]
- search: {
    "pattern": "(?i)\\b(main|wmain|winmain|dllmain|start|entry)\\b",
    "target_type": "symbol"
  }

Tool 返回:

1
2
3
4
5
6
# Search Results
- total_count: 2
 
## Items
1. [symbol/function] main @ 0x1140
2. [symbol/function] start @ 0x14c0

找到了两个关键符号:mainstart。接下来需要确认它们的调用关系。

1
2
[Tool Calls]
- xref: {"target": "main", "target_type": "symbol", "direction": "to"}

Tool 返回:

1
2
3
4
5
6
7
8
9
# Xref Results
- total_count: 5
 
## Items
1. ea=0x3fd8 -> target=0x4060 [__libc_start_main]
2. start+0x1f -> target=0x4060 [__libc_start_main]
3. start+0x18 -> target=0x1140 [main]     ← 关键证据
4. start+0x1f -> target=0x3fd8 [__libc_start_main_ptr]
5. ea=0x420 -> target=0x56e [aLibcStartMain]

证据解读:

xref 的结果清晰地展示了调用链:start 函数在偏移 +0x18 处调用了 main(地址 0x1140)。这是标准的 C 程序启动流程:start 作为真正的程序入口,通过 __libc_start_main 最终调用用户定义的 main

Agent 立即更新任务状态,记录这一关键证据:

1
2
3
4
5
6
[Tool Calls]
- set_task_status: {
    "task_ref": "t001",
    "status": "in_progress",
    "note": "已定位入口符号: main@0x1140, start@0x14C0;xref 证据: start+0x18 -> main"
  }

第二轮:关键函数反编译与证据采集

入口定位完成,接下来深入分析 main 函数。

1
2
[Tool Calls]
- decompile_function: {"function_name": "main"}

Tool 返回的伪代码(节选):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __int64 ptr; // [rsp+8h] [rbp-50h]
   
  ptr = (__int64)calloc(1u, 0x28u);           // 分配 0x28 字节
  if ( ptr )
  {
    *(_DWORD *)(ptr + 8) = 256;               // offset +8 写入 256
    *(_QWORD *)ptr = 0x5244485F544E4F43LL;    // offset +0 写入 magic
    *(_QWORD *)(ptr + 32) = sub_15B0;         // offset +32 写入函数指针
    // ...
    v6 = calloc(1u, 0x38u);                   // 分配 0x38 字节
    // ...
    v10 = calloc(v7, 0x38u);                  // 分配数组,元素大小 0x38
  }
}

关键发现:

  1. ptr = calloc(1, 0x28) —— 分配 40 字节,很可能是某个结构体
  2. *(ptr + 8) = 256 —— offset 8 可能是版本号字段
  3. *ptr = 0x5244485F544E4F43 —— offset 0 写入一个 8 字节常量,可能是 magic
  4. *(ptr + 32) = sub_15B0 —— offset 32 写入函数指针
  5. 后续还有 calloc(1, 0x38)calloc(v7, 0x38) —— 说明还有另外两种大小为 56 字节的结构体

但仅凭 main 的代码还不够,Agent 决定同时反编译 main 中调用的子函数,获取更完整的字段使用证据:

1
2
3
4
5
[Tool Calls]
- decompile_function: {"function_name": "sub_1780"}
- decompile_function: {"function_name": "sub_15B0"}
- decompile_function: {"function_name": "sub_15F0"}
- decompile_function: {"function_name": "sub_16C0"}

sub_1780 的反编译结果(应用类型前):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall sub_1780(__int64 a1, int a2)
{
  __int64 v2;
   
  if ( !a1 )
    return v13 - __readfsqword(0x28u);
  v2 = *(_QWORD *)(a1 + 16);                  // offset +16 作为链表头
  if ( !v2 )
    goto LABEL_13;
  while ( *(_DWORD *)v2 != a2 )               // 比较 offset 0
  {
    v2 = *(_QWORD *)(v2 + 32);                // offset +32 作为 next 指针
    if ( !v2 )
      goto LABEL_13;
  }
  v4 = (*(__int64 (**)(void))(a1 + 32))(v2, 16, 0);
  // ... 更多字段访问 ...
}

证据解读:

  • a1 + 16 被解引用作为链表头,说明 offset 16 是 head 指针
  • v2 + 32 是 next 指针,说明 node 结构体的 offset 32 是 next
  • a1 + 32 被当作函数指针调用,验证了 main*(ptr + 32) = sub_15B0 的推测

sub_15F0 的反编译结果(应用类型前):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall sub_15F0(__int64 a1)
{
  __int64 v1;
   
  v1 = *(_QWORD *)(a1 + 16);                  // offset +16 作为某个指针
  if ( v1 )
  {
    v4 = *(unsigned int *)(a1 + 24);          // offset +24 是计数
    if ( (_DWORD)v4 )
    {
      v5 = *(_QWORD *)(a1 + 8) & 0xFFFLL;     // offset +8 被读取
      v6 = v1 + 56 * v4;
      do
      {
        *(_DWORD *)(v1 + 52) |= 1u;           // offset +52 被置位
        *(_QWORD *)(v1 + 40) += v5;           // offset +40 被累加
        v1 += 56;                             // 步长 56,说明是数组
      }
      while ( v1 != v6 );
    }
  }
}

证据解读:

  • a1 + 16 指向的内存以 56 字节为步长遍历,且每个元素有 offset 52 和 40 的访问
  • 这与 maincalloc(v7, 0x38)(0x38 = 56)对应,说明 v1 指向的是 field_desc 数组

Agent 更新任务状态,记录已采集的证据:

1
2
3
4
5
6
[Tool Calls]
- set_task_status: {
    "task_ref": "t002",
    "status": "in_progress",
    "note": "已反编译 main@0x1140 获得容器与节点构建逻辑;已提取符号使用:calloc(0x28) 容器、calloc(0x38) 节点、calloc(v7,0x38) 字段数组"
  }

第三轮:结构体建模与创建

基于前面收集的证据,Agent 开始构建结构体定义。

container 结构体推导:

偏移 大小 证据来源 语义推断
+0x00 8 *ptr = 0x5244485F544E4F43 magic 字段
+0x08 4 *(ptr + 8) = 256 version
+0x0C 4 ++*(ptr + 12) node_count
+0x10 8 *(ptr + 16) 作为链表头 head 指针
+0x18 8 *(ptr + 24) 指向尾节点 tail 指针
+0x20 8 *(ptr + 32) = sub_15B0 hash 函数指针

总大小:0x28(40 字节),与 calloc(1, 0x28) 吻合。

node 结构体推导:

偏移 大小 证据来源 语义推断
+0x00 4 *v6 = v33 id
+0x04 4 v6[1] = v4 flags
+0x08 8 *(v8 + 8) = time(0) ts(时间戳)
+0x10 8 *(v8 + 16) = v11 fields 指针
+0x18 4 v8[6] = v7 field_count
+0x1C 4 未观察到访问 unk1C(占位)
+0x20 8 遍历 v2 + 32 next 指针
+0x28 8 双向链表逻辑 prev 指针
+0x30 8 *(v8 + 48) = v20 process 函数指针

总大小:0x38(56 字节),与 calloc(1, 0x38) 吻合。

field_desc 结构体推导:

偏移 大小 证据来源 语义推断
+0x00 32 snprintf(..., 32, ...) 写入 name name[32]
+0x20 4 *(v14 - 6) = v12 & 3 kind
+0x24 4 未观察到访问 unk24
+0x28 8 *(v14 - 2) = 8 * v12 value
+0x30 4 *(v14 - 1) = 4 << (v12 % 3) size
+0x34 4 sub_15F0 中置位操作 flags

Agent 使用 create_structure 创建这三个结构体:

1
2
3
4
[Tool Calls]
- create_structure: {"name": "field_desc", "c_decl": "struct field_desc { char name[32]; unsigned int kind; unsigned int unk24; unsigned __int64 value; unsigned int size; unsigned int flags; };"}
- create_structure: {"name": "node", "c_decl": "struct node { unsigned int id; unsigned int flags; unsigned __int64 ts; struct field_desc *fields; unsigned int field_count; struct node *next; struct node *prev; void (*process)(struct node*); };"}
- create_structure: {"name": "container", "c_decl": "struct container { unsigned __int64 magic; unsigned int version; unsigned int node_count; struct node *head; struct node *tail; __int64 (*hash)(void *buf, __int64 len, __int64 unused); };"}

Tool 返回(确认结构体已落地):

1
2
3
struct field_desc { ... }; // size=0x38 (56), mutation_effective=true
struct node { ... }; // size=0x38 (56), mutation_effective=true
struct container { ... }; // size=0x28 (40), mutation_effective=true

mutation_effective=true 表示结构体已成功写入 IDA 数据库。这是关键一步:结构体不再是"纸面分析",而是真正落地到了 IDB 中


第四轮:类型应用与验证闭环

结构体创建完成,但这只是开始。接下来必须验证:应用这些类型后,反编译代码是否真的变得更易读了?

Agent 在 main 函数中应用类型:

1
2
3
4
5
6
7
8
9
[Tool Calls]
- set_identifier_type: {
    "function_name": "main",
    "operations": [
      {"kind": "local", "name": "ptr", "c_type": "struct container *"},
      {"kind": "local", "name": "v8", "c_type": "struct node *"}
    ],
    "redecompile": true
  }

Tool 返回(重反编译后的 main 函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
__int64 __fastcall main(int a1, char **a2, char **a3)
{
  struct container *ptr;
   
  ptr = (struct container *)calloc(1u, 0x28u);
  if ( ptr )
  {
    ptr->version = 256;
    ptr->magic = 0x5244485F544E4F43LL;
    ptr->hash = (__int64 (*)(void *, __int64, __int64))sub_15B0;
    // ...
    v8->fields = v11;
    v8->process = v20;
    if ( ptr->head )
    {
      tail = ptr->tail;
      ptr->tail = v8;
      v8->prev = tail;
      tail->next = v8;
    }
    // ...
    head = ptr->head;
    for ( i = head; i; i = i->next )
    {
      process = i->process;
      if ( process )
        process(i);
    }
  }
}

验证效果:

  • ptr->version = 256 替代了 *(_DWORD *)(ptr + 8) = 256
  • ptr->magic 替代了直接赋值常量
  • v8->fieldsv8->processv8->prevv8->next 全部变成了有意义的字段名
  • 链表遍历 i = i->next 清晰可读
  • 函数调用 process(i) 不再是一堆强制类型转换

同样的验证在子函数中进行:

重反编译后的 sub_1780:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
__int64 __fastcall sub_1780(struct container *a1, unsigned int a2)
{
  struct node *head;
   
  head = a1->head;
  while ( head->id != a2 )
  {
    head = head->next;
    if ( !head )
      goto LABEL_13;
  }
  v4 = a1->hash(head, 16, 0);
  field_count = head->field_count;
  if ( (_DWORD)field_count )
  {
    fields = head->fields;
    while ( 1 )
    {
      field_count = fields->size + field_count;
      if ( fields->value > 0x10000 || (fields->flags & 4) != 0 )
        break;
      if ( ++fields == v8 )
        return;
    }
  }
}

重反编译后的 sub_15F0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall sub_15F0(struct node *a1)
{
  struct field_desc *fields;
   
  fields = a1->fields;
  if ( fields )
  {
    field_count = a1->field_count;
    if ( (_DWORD)field_count )
    {
      v5 = a1->ts & 0xFFF;
      v6 = &fields[field_count];
      do
      {
        fields->flags |= 1u;
        fields->value += v5;
        ++fields;
      }
      while ( fields != v6 );
    }
  }
}

重反编译后的 sub_16C0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned __int64 __fastcall sub_16C0(struct node *a1)
{
  struct field_desc *fields;
   
  fields = a1->fields;
  if ( fields )
  {
    field_count = a1->field_count;
    if ( (_DWORD)field_count )
    {
      v5 = &fields[field_count];
      do
      {
        fields->flags |= 2u;
        if ( fields->kind == 1 )
          fields->size *= 2;
        ++fields;
      }
      while ( v5 != fields );
    }
  }
}

闭环验证总结:

函数 应用前 应用后 改进
main *(_DWORD *)(ptr + 8) = 256 ptr->version = 256 字段语义清晰
main v2 = *(_QWORD *)(a1 + 16) head = a1->head 链表操作可读
sub_1780 *(_QWORD *)(a1 + 32))(v2, 16, 0) a1->hash(head, 16, 0) 函数指针调用明确
sub_15F0 `*(_DWORD *)(v1 + 52) = 1` `fields->flags
sub_16C0 v1[8] == 1 fields->kind == 1 条件判断可读

Agent 更新任务状态,标记结构体恢复任务完成:

1
2
3
4
5
6
[Tool Calls]
- set_task_status: {
    "task_ref": "t003",
    "status": "done",
    "note": "结构体已创建并通过类型应用验证闭环:create_structure 确认大小;set_identifier_type 后伪代码字段访问全部替换为有名字段"
  }

第五轮:知识沉淀与任务闭环

所有子任务完成后,Agent 将关键发现沉淀到知识区:

1
2
3
4
5
6
7
8
[Tool Calls]
- knowledge_write: {
    "confirmed_facts": "main 使用 calloc(1,0x28) 分配容器对象...",
    "evidence": "search: main@0x1140; xref: start+0x18 -> main; decompile main...",
    "hypotheses": "container::hash 的第三参数目前固定为 0,可能是保留参数",
    "open_questions": "field_desc::unk24 未见读写证据,需在其它函数中找交叉引用",
    "do_not_repeat": "不在无偏移访问证据时猜测字段含义"
  }

这种知识沉淀机制使得分析过程可持续:如果后续发现新的证据,可以回到知识区更新假设;如果另一个子 Agent 在别处发现了 unk24 的访问,可以补充到这里。

最终的任务板状态:

1
2
3
4
5
6
7
## Task Status (Current)
- summary: total=4, active=0, done=4, blocked=0
 
- [x] t001 (high) 定位关键函数入口/热点函数 [status=done]
- [x] t002 (high) 采集关键函数伪代码与符号使用证据 [status=done]
- [x] t003 (high) 创建/迭代结构体定义并应用类型验证 [status=done]
- [x] t004 (normal) 形成证据链报告 [status=done]

所有任务都已闭环,Agent 提交最终输出:

1
2
3
4
5
6
[Tool Calls]
- submit_output: {
    "summary": "已完成关键函数分析并恢复 3 个核心结构体(container/node/field_desc)",
    "key_findings": "入口: main@0x1140; container 大小 0x28; node 大小 0x38; field_desc 大小 0x38",
    "artifacts": "IDA Structures: container/node/field_desc; 类型应用验证: main/sub_1780/sub_15F0/sub_16C0"
  }

六、Tool Call 逻辑:为什么强调 docstring 而不是 JSON Schema 堆叠

项目里 tool 调用遵循几条硬规则:

  1. bind_tools + docstring 是参数语义基线
  2. 参数要显式且最小,避免盲调
  3. Tool 返回 ERROR: 时先做最小修复再重试
  4. 有依赖关系的工具必须分轮调用
  5. 每轮结论必须引用工具证据文本

从实现上看,我们采用了"动态绑定工具集":

  • 主循环动态注入运行时工具(任务板、知识库、上下文压缩、submit)
  • 子循环使用子 Agent 对应工具集
  • 工具执行支持受控并发(有并发上限和错误收集)

这套设计的优势是:策略灵活,但工具边界清晰


七、IDAPython Agent 的 CodeAct 设计:核心差异化

很多系统把"执行 IDAPython"当成一个黑盒工具:脚本失败了就失败了。我们做的是"脚本失败也能自修复"的可循环执行器。

1)入口:先执行,再决定是否进入修复环

execute_idapython 的路径大致是:

  • 先跑一次脚本
  • 如果成功,直接返回
  • 如果失败,进入 IDAPython repair 子循环

2)修复环:观察错误 -> 最小修改 -> 提交执行 -> 再观察

系统会自动生成子任务板,例如:

  • 已执行初始脚本并收集错误
  • 待最小修改并提交
  • 若知识不足则检索 KB(search/read_file
  • 成功后退出

这正是 CodeAct 思路在工程里的落地:

  • 行动是"可执行代码"
  • 观察是"运行输出和报错"
  • 下一步动作由上一轮执行结果驱动

3)安全与边界:防破坏优先

我们明确阻断了破坏性结构操作,例如 idc.del_struc。理由很简单:一旦删错结构体,证据链和验收基线都会被污染。

4)IDA 9.3 兼容策略

我们在提示词和执行器里都写了"高风险 API 规避策略":

  • 避免盲用兼容性差的接口
  • 优先 idc/idautils/ida_hexrays 等稳定模块
  • 每次修复只改报错相关行,拒绝大改

这套机制让"会写脚本"升级成"可控地修脚本并完成任务"。


八、任务板与知识库:让 Agent 不是"边跑边忘"

我们把任务管理做成了第一等公民,而不是日志附属品。

任务板支持:

  • create_task(单条/批量)
  • set_task_status(todo/in_progress/blocked/done/cancelled)
  • edit_task
  • get_task_board

知识库支持:

  • confirmed_facts —— 已验证的事实
  • hypotheses —— 待验证的假设
  • open_questions —— 尚未解答的问题
  • evidence —— 证据来源
  • next_actions —— 下一步建议
  • do_not_repeat —— 避免重复踩坑

为什么这很关键?因为它把"模型短期上下文"转换成"可持续记忆"。尤其在长链路逆向任务里,没有这层结构化记忆,系统几乎一定会重复劳动。


九、上下文压缩:长任务场景里必须有"记忆管理"

长周期分析常见问题是上下文污染:历史消息越多,模型越容易掉焦点。我们设计了两层机制:

  1. prune_context_messages:按 Message ID 定点折叠
  2. compress_context_8block:触发 8-block 蒸馏快照

蒸馏不是"摘要文学",而是保留可执行记忆:

  • 当前目标
  • 关键证据
  • 任务状态
  • 禁止重复动作
  • 下一步建议

这让系统可以在长会话中保持"方向稳定 + 成本可控"。


十、可观测性与验收

1)可观测性

所有会话进入 SQLite,包含:

  • sessions —— 会话级别元数据
  • turns —— 轮次级别信息
  • messages —— 完整消息链
  • turn_tools —— 工具调用记录
  • session_events —— 事件日志

并配有前端时间线,可回看"某一轮为什么调用了这个工具、失败点是什么、后续如何修复"。

2)验收

run_reverse_expert_agent.py 在执行期自动完成:

  • IDB 备份
  • before/after 结构体快照
  • struct diff
  • acceptance summary(md/json)

而且定义了明确失败条件:

  • 无 tool call
  • 无有效 mutation
  • before/after 无差异
  • 运行异常中断

这意味着:系统不是"跑完就算成功",而是"满足质量门槛才算成功"


十一、关键设计解析

1. 为什么坚持"类型应用后必须重反编译验证"

很多自动化工具止步于"输出结构体定义",但 Agentic IDA Pro 强制要求验证闭环。这是因为:

  • 模型可能猜错:LLM 根据偏移推断的字段语义,可能与实际不符。只有看到 ptr->version = 256 这样的代码,才能确认 offset 8 真的是版本号。
  • 结构体定义可能不完整:如果漏掉了某个字段,后续访问会出现错位。重反编译能立即暴露这种问题。
  • 可读性是硬指标:结构体恢复的目标是让代码更易读。如果应用类型后代码仍然满屏强制类型转换,说明恢复工作还没到位。

2. 为什么用"任务板"管理状态

长链任务中,LLM 容易"遗忘"之前做过什么。任务板的价值在于:

  • 外化记忆:不依赖 LLM 的上下文记忆,而是把状态写在持久化的任务板里
  • 可审计:每个任务的完成都有明确的证据引用
  • 阻断提交:如果还有未完成的任务,submit_output 会被拒绝,防止"半成品"输出

3. 为什么需要"知识区"

与任务板不同,知识区沉淀的是跨任务的长期认知。这使得多个子 Agent 可以协作分析:一个 Agent 发现了线索,写入知识区;另一个 Agent 在别处找到了答案,回来补充。


十二、与学术/行业思路的关系:借鉴与工程化

从研究脉络看,我们受到两类工作启发:

  1. ReAct:交错的推理与行动
  2. CodeAct:把可执行代码作为统一行动空间

但项目不是直接照搬论文,而是做了工程化改造:

  • 将"动作空间"映射为可控工具集(含逆向专用工具)
  • 用任务板和知识区约束长链路决策
  • 用子 Agent 做角色隔离,降低上下文污染
  • 用验收机制保证"可交付"的底线

简单说:论文解决"能不能做",工程系统解决"能不能长期稳定地做"


十三、风险控制与局限

风险控制

风险 对策
模型幻觉 每步操作必须有工具证据,不能只凭"我觉得"
破坏性操作 显式阻断删除结构体等高危 API
失败不可追溯 所有操作写入 SQLite,支持轮级回放
伪成功 必须有实际变更(结构体 diff)才算完成
无限循环 最大迭代次数 + 子任务超时机制

当前局限

  • 仅支持 IDA Pro 9.3
  • 当前优化重点是结构体恢复,不代表覆盖所有逆向场景
  • 字段命名质量取决于证据密度

路线图

  1. 增加结构体恢复质量的自动化评测
  2. 扩展更多逆向技能(字符串解密、协议字段恢复、语义级的代码分析)
  3. 更细粒度的变更审计(字段变更原因链)
  4. RAG 增强逆向workflow, 和 IDAPython 脚本开发

十四、如何快速上手

1)环境要求

  • IDA Pro 9.3
  • Python 3.10+
  • OpenAI API Key(或其他兼容 OpenAI 格式的端点)

2)配置模型环境

1
2
3
export OPENAI_API_KEY='your-api-key'
export OPENAI_BASE_URL='http://your-llm-endpoint/v1'
export OPENAI_MODEL='gpt-5.2'

3)启动 IDA Service(Windows)

.\src\scripts\run_windows_bridge.bat

4)启动 Agent(WSL/Linux)

1
2
3
4
PYTHONPATH=src python src/scripts/run_reverse_expert_agent.py \
  --ida-url http://127.0.0.1:5000 \
  --request "分析关键函数并恢复结构体定义,给出证据链" \
  --max-iterations 40

5)运行前检查清单


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 21小时前 被xxxxxxx_798269编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (6)
雪    币: 1014
活跃值: (177)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
运行前检查清单 后面的内容好像被截断了
21小时前
0
雪    币: 253
活跃值: (1428)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
很厉害啊,晚上回去试一下
21小时前
0
雪    币: 4
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
能分享下配置文件吗
16小时前
0
雪    币: 1014
活跃值: (177)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
RRRRoma 能分享下配置文件吗
请问下什么配置文件
6小时前
0
雪    币: 5847
活跃值: (10107)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
你是真牛逼
4小时前
0
雪    币: 377
活跃值: (2487)
能力值: ( LV8,RANK:140 )
在线值:
发帖
回帖
粉丝
7
挺好
2小时前
0
游客
登录 | 注册 方可回帖
返回