rea-reverse

用 AI 逆向 Windows EXE/DLL:REA + Ghidra 实操

基于 REA 6.4.0更新于 约 8 分钟读完
本页目录

一句话版本: REA(morluto 维护的开源项目)通过无头 Ghidra provider 分析原生 Windows PE 文件,另外还带一个完全不需要分析引擎的 PE 资源解析器。做这件事不一定要有 Windows 机器:Linux 或 macOS 上的 Ghidra 就能处理 x86 和 x86-64 PE 文件。在 Windows 主机上,REA 6.4.0 的 Ghidra 支持是实验性、只读的 P0,准入范围很窄。本文先从源码编译一个小 DLL 和 EXE,再分别用 CLI 和 AI agent 走一遍 PE 分析流程:识别文件类型、导出、导入、资源、字符串、反编译。

本文面向想看懂某个 PE 文件的开发者,前提是你有权分析它:自己的构建、依赖库,或者开源软件的发布版。假设你已经按 REA + Ghidra 入门篇配好了 REA 和 Ghidra。建议先看一眼如何确认装的是真 REA:REA 通过 npm 以 rea-agents 包分发,从来不提供单独下载的 .exe。

先决定在哪台机器上分析

先选主机,因为主机决定了你能用哪些功能。下表是 6.4.0 的实际情况,依据是发布包里的 provider 代码:

Linux / macOS 主机 Windows x64 主机(P0)
状态 正式支持 实验性
PE 目标 架构为 x86、x86-64、ARM 或 ARM64 的可执行文件(跨格式验证覆盖的是 x86-64 PE) 仅原生 x86 和 x86-64 PE 应用程序和 DLL
Ghidra 处理托管(.NET)PE 改用托管代码工具 拒绝
重命名 / 注解 支持(set_addresses_names、annotate_native_function) 不支持:禁止修改数据库
分析后的 no-return 修复 有 关闭
目标位置 任意本地路径 固定的本地 NTFS 卷。UNC 路径、重解析点、备用数据流和其他文件系统都会被拒绝
额外要求 无 Windows 10+,以及带配套原生插件的 Windows x64 包
capture-process(运行时) Linux、macOS 原生 Windows 不可用,REA 会提示 Linux 命令改用 Linux/WSL

实用建议:能在 Linux 或 macOS 上分析 PE,就在那里分析;只有文件不能离开 Windows 机器时,才用 Windows P0。两边得到的静态分析结果都是同一种 Evidence。

前置条件

  • REA 6.4.0(npm install --global rea-agents),配好 Ghidra 12.1.x 和 64 位 JDK 21+,并且 rea doctor --provider ghidra --json 能通过。
  • 面向 Windows 的 C 工具链:Linux/macOS 上用 MinGW-w64(x86_64-w64-mingw32-gcc、windres),Windows 上用 MSVC(cl、rc)。
  • 如果要用 agent,还需要一个注册了 REA 的 MCP 客户端(rea setup)。

在 Windows 上,先在 PowerShell 里设好路径,再运行 doctor 和 setup:

$env:GHIDRA_INSTALL_DIR = "C:\tools\ghidra_12.1.4_PUBLIC"
$env:JAVA_HOME = "C:\tools\jdk-21"
rea doctor --provider ghidra --json
rea setup

这些路径以及目标文件路径里都不要出现 " % ^ ! & | < >。6.4.0 的 Windows 启动器会拒绝传给 Ghidra 批处理脚本的路径里含有命令解释器元字符。

第 1 步:编译一个练习用的 DLL 和 EXE

目标是自己写的,agent 说的每一句话都能对照源码核实。这个 DLL 有两个导出函数,导入了几个 Win32 文件 API,还带一份版本资源和一个字符串表。

greet.c:

#include <windows.h>
#include <string.h>

__declspec(dllexport) unsigned greet_checksum(const char *name) {
    unsigned h = 2166136261u;                 /* FNV-1a */
    for (; *name; name++) { h ^= (unsigned char)*name; h *= 16777619u; }
    return h;
}

__declspec(dllexport) int greet_write_log(const char *path, const char *name) {
    HANDLE f = CreateFileA(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL, NULL);
    if (f == INVALID_HANDLE_VALUE) return 0;
    DWORD written = 0;
    const char *prefix = "greet-log: hello, ";
    WriteFile(f, prefix, (DWORD)strlen(prefix), &written, NULL);
    WriteFile(f, name, (DWORD)strlen(name), &written, NULL);
    CloseHandle(f);
    return 1;
}

greet.rc:

#include <winver.h>
1 VERSIONINFO
FILEVERSION 1,2,0,0
PRODUCTVERSION 1,2,0,0
BEGIN
  BLOCK "StringFileInfo"
  BEGIN
    BLOCK "040904B0"
    BEGIN
      VALUE "FileDescription", "Greet demo library"
      VALUE "FileVersion", "1.2.0.0"
      VALUE "ProductName", "REA practice target"
    END
  END
  BLOCK "VarFileInfo"
  BEGIN
    VALUE "Translation", 0x409, 1200
  END
END

STRINGTABLE
BEGIN
  101 "Greeting log written"
END

main.c:

#include <stdio.h>
__declspec(dllimport) unsigned greet_checksum(const char *name);
__declspec(dllimport) int greet_write_log(const char *path, const char *name);

int main(int argc, char **argv) {
    const char *who = argc > 1 ? argv[1] : "world";
    printf("checksum=%08x\n", greet_checksum(who));
    return greet_write_log("greet.log", who) ? 0 : 1;
}

在 Linux/macOS 上用 MinGW-w64 编译:

x86_64-w64-mingw32-windres greet.rc -O coff -o greet_res.o
x86_64-w64-mingw32-gcc -O1 -shared -o greet.dll greet.c greet_res.o -Wl,--out-implib,libgreet.a
x86_64-w64-mingw32-gcc -O1 -o hello.exe main.c -L. -lgreet
DLL="$PWD/greet.dll"; EXE="$PWD/hello.exe"

或者在 Windows 的 Developer PowerShell 里用 MSVC:

rc greet.rc
cl /O1 /LD greet.c greet.res
cl /O1 main.c greet.lib

练熟之后想换个大一点的开源目标,可以用 7-Zip 官方发布的 Windows 二进制。7-Zip 采用 LGPL 许可,源码公开,可以用来核对 agent 的结论。

第 2 步:先识别文件,再选分析路线

.dll 扩展名说明不了里面是什么:可能是原生代码,可能是 .NET 程序集,也可能是 NativeAOT 映像。先让 REA 的托管代码解析器看一眼,它只读文件头,不执行任何东西:

rea inspect-managed-artifact "$DLL" --json

我们的原生 DLL 应该被判定为非托管。如果它报告这是托管程序集,就停下来,改看 .NET 程序集静态分析;反正 Windows P0 也不接受用 Ghidra 分析托管 PE。NativeAOT 映像是原生代码,仍然走本文的路线,另外再加上那篇 .NET 文章里讲的元数据步骤。

然后获取原生概览:

rea analyze "$DLL" --provider ghidra --json > greet-overview.json

概览会列出各个段(Ghidra 映射出的 PE 节,含起始地址、结束地址和长度),以及函数数量和字符串数量。记下映像基址:Ghidra 通常把 PE 头映射为地址最低的段,起点就是映像基址。第 4 步要用它把 RVA 换算成地址。

第 3 步:导出和导入

REA 6.4.0 没有专门“列出导出”的 CLI 命令,这些信息来自 Ghidra 的清单,REA 会原样返回相关标志:

  • 导入出现在函数清单里,标记为 external: true,通常还有本地 thunk(thunk: true 和 thunk_target);在名称清单里,它们是外部符号。
  • 导出是外部入口点。inspect-native-load-image 把它们和 PE 入口例程一起放在 entry_points 里返回。
rea inspect-native-load-image "$DLL" --provider ghidra --json > greet-load.json
rea search "$DLL" greet_ --kind procedures --provider ghidra --json
rea search "$DLL" CreateFile --kind procedures --provider ghidra --json

有两点要注意。第一,对 PE 文件,inspect-native-load-image 返回的测量结果带一个 unsupported 的*验证(attestation)*状态,意思是 REA 没有独立复核 PE 加载映像,不是文件加载失败。第二,Ghidra 会给导入函数名加上库的命名空间,传给 xrefs 之前,要把名字按清单返回的样子原样复制。

接下来回答分析 DLL 时最关键的问题:哪个导出函数会调到哪个导入函数?

rea xrefs "$DLL" 0xCREATEFILEA_THUNK_ADDRESS --provider ghidra --json
rea function "$DLL" greet_write_log --provider ghidra --json

第一条命令应该指回 greet_write_log。函数档案会列出它调用的函数(CreateFileA、WriteFile、CloseHandle)、引用的字符串和伪代码,每一项都带 Evidence。inspect-native-api 更进一步:它分析单个函数的 API 边界,返回置信度、Evidence、跳转表分派映射、不支持的分支和剩余未知项:

rea inspect-native-api "$DLL" greet_write_log --provider ghidra --json

第 4 步:资源

PE 资源里放着版本信息、图标、清单(manifest)、对话框和字符串表,往往是了解一个二进制“自称是什么”最快的入口。REA 6.4.0 的 inspect-pe-resources 直接解析资源树,不需要 Ghidra、不需要 Windows SDK,也不执行文件,任何主机都能用:

rea inspect-pe-resources "$DLL" --json > greet-res.json

对每个资源,它会返回类型、名称和语言标识(数字 ID 或原始 UTF-16 名称),目录项和数据项在文件里的位置,以及载荷的 RVA、文件范围和 SHA-256。图标组会关联到对应的候选图像资源。默认读取预算是 64 MiB(--max-file-bytes),最多检查 4,096 个目录项(--max-entries)。超过任何一个上限,命令都会失败,不返回部分结果。

还要知道它不做什么:它不解码载荷内容。你会看到一个类型 16(RT_VERSION)的资源和它的文件范围,但看不到 “Greet demo library” 这几个字。要读内容,就用它报告的载荷范围:

xxd -s PAYLOAD_OFFSET -l PAYLOAD_BYTES greet.dll     # version strings are UTF-16LE
rea read-bytes "$DLL" 0xIMAGE_BASE_PLUS_RVA --length 256 --provider ghidra --json

第二种方式读的是 Ghidra 加载后的内存,读出的字节会附带 Evidence。其他常见类型 ID:6 是字符串表块,14 是图标组,24 是清单。

第 5 步:字符串和反编译

剩下的就是 Ghidra 入门篇里那套熟悉的流程,只是换成了 PE:

rea search "$DLL" "greet-log" --provider ghidra --json
rea decompile "$DLL" greet_checksum --provider ghidra --json
rea trace "$EXE" "checksum=" --provider ghidra --json

greet_checksum 的伪代码里应该能看到 FNV-1a 的常量(0x811c9dc5、0x1000193)。这种常量很容易对照源码核实,能认出它们的 agent 应该直接点出来。对 EXE 运行 trace,会从格式字符串追回 main,再追到对 greet.dll 的导入调用。DLL 那一侧要单独分析,因为每个 REA Ghidra 会话只导入一个程序。

在 Ghidra 里分析 PE 有两个注意事项:

  • Ghidra 的字符串清单只覆盖它定义成字符串数据的内容。宽字符串或运行时拼出来的字符串可能不在里面,所以搜不到不代表文本不存在。
  • REA 6.4.0 没有指定 PDB 的设置,而且 Ghidra 导入的是你文件的私有副本,所以别指望它读到二进制旁边的 .pdb。导出名来自导出表,不受影响;内部函数如果不手动命名,就只有按地址生成的名字。想让命名持久保留,可以用种子文件,见 Ghidra 无头模式调优。

第 6 步:在 agent 里提问

CLI 适合写脚本,agent 适合判断下一步该看什么。指定 provider 打开文件一次,这样开销最大的导入在一个会话里只做一次:

Use REA with provider ghidra to open /absolute/path/to/greet.dll.
1. Tell me what kind of PE this is (architecture, DLL vs EXE, managed or native).
2. List its exports and, for each export, the imported Win32 APIs it can reach.
3. Inspect its PE resources and decode the version information from the
   resource payload bytes.
Cite the Evidence for each claim and list anything you could not resolve.

分析 DLL 时好用的追问:

Which exported functions write to the filesystem? Show the call path from the
export to the import and the pseudocode lines involved.
Decompile greet_checksum, name the algorithm if you recognize it, and show the
instructions or constants that support that identification.
Compare what the version resource claims with what the exports actually do.
Flag anything the metadata does not explain.

让回答保持可靠的几个习惯:

  • 每次都要求给出 Evidence 和未知项。 对没解析出来的计算调用,REA 会标为未知而不是瞎猜;好的回答应该把这些未知项如实转述。
  • 让 agent 读字节,不要让它猜。 资源内容、数据表之类,要它对报告出的范围调用 read_bytes。
  • 对照源码核实。 这就是练习目标要用自己编译的程序的原因。
  • 在 Windows 上别让它重命名。 P0 是只读的,set_addresses_names 和 annotate_native_function 都不可用。让 agent 在回复里维护一份名称对照表就行。

常见问题和排错

doctor 报告 Windows 原生控制组件不可用。 Windows P0 需要带配套原生插件的 Windows x64 包。在那台机器上用 npm 重新安装 rea-agents。改 MCP 注册配置补不上这个插件。

Windows 上报 target_format_unsupported、architecture_unsupported 或 managed_target_unsupported。 P0 只接受原生 x86/x86-64 PE 应用程序和 DLL。ARM64 PE、.NET 程序集和非 PE 文件按设计就会被拒绝。换到 Linux 或 macOS 上分析,或者改用托管代码工具。

Windows 上路径被拒绝。 把目标复制到固定本地 NTFS 盘上的普通文件夹。UNC/网络路径、重解析点(junction、符号链接)、备用数据流和含元字符的路径都会被拒绝;subst 映射的盘符也不在验证范围内。

Windows 上 JAVA_HOME 或 Ghidra 路径出问题。 用绝对路径,最好全是 ASCII 字符、不含元字符,然后重新运行 rea doctor --provider ghidra --json,它会指出是哪项检查失败。

agent 第一个问题就超时。 Ghidra 冷启动导入可能超过客户端默认的工具调用超时。REA 自己的超时是 330,000 毫秒(REA_GHIDRA_STARTUP_TIMEOUT_MS)。调大客户端的超时,关闭再重开的恢复流程见 无头模式调优。

想看运行时行为。 6.4.0 的进程捕获不能在原生 Windows 上运行,Windows 上受支持的是静态分析。

合规使用

请用自己编译的程序、7-Zip 这类开源软件的发布版、CTF 题目或 REA 官方 showcase 的目标来练习。这套流程是用来理解你有权检查的软件的,不能用来绕过授权或激活机制。分析不是自己编译的东西之前,先读 REA 的 DISCLAIMER.md。

下一步