rea-reverse

用 REA 检查 Windows EXE 的图标、版本资源和嵌入数据

基于 REA 6.8.0发布者 rea-reverse更新于 约 3 分钟读完
本页目录

一个 EXE 里除了代码,还可能放着图标、版本信息和应用数据。比较两个构建产物时,先弄清楚资源里有什么、哪一份数据发生了变化,往往比直接打开程序更有用。

REA 的 inspect-pe-resources 可以列出 PE 资源树、计算每个资源数据的哈希,并把图标组关联到候选图标资源。它不会解码版本字符串,也不能证明 Windows 最终会显示哪个图标;这些问题需要其他解析工具或真实 Windows 环境来回答。

本文自己构造了一个 EXE,只检查文件,没有运行它。测试环境是 macOS、REA 6.8.0、Node 26.12.1、Zig 0.17.0 和 pefile 2024.8.26。REA 6.8.0 要求 Node 22.x 至少 22.23.4、24.x 至少 24.22.1,或 26.12.1 及以上;Node 23 和 25 不受支持。换一套工具链或构建参数,EXE 的哈希和偏移量可能不同。

构造一个带资源的 EXE

先创建目录、程序和数据文件:

mkdir pe-demo && cd pe-demo
printf 'int main(void) { return 0; }\n' > main.c
printf 'REA resource demonstration\n' > payload.bin

用 Python 标准库生成一个 16×16、内部使用 PNG 的 ICO。把以下代码保存为 make_icon.py,然后运行 python3 make_icon.py:

import struct, zlib
from pathlib import Path
w = h = 16
raw = b''.join(b'\0' + bytes([28, 110, 190, 255]) * w for _ in range(h))
def chunk(kind, data):
    return (struct.pack('>I', len(data)) + kind + data
            + struct.pack('>I', zlib.crc32(kind + data) & 0xffffffff))
png = (b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 6, 0, 0, 0))
       + chunk(b'IDAT', zlib.compress(raw)) + chunk(b'IEND', b''))
header = struct.pack('<HHH', 0, 1, 1)
entry = struct.pack('<BBBBHHII', w, h, 0, 0, 1, 32, len(png), 22)
Path('demo.ico').write_bytes(header + entry + png)

再创建 demo.rc,把图标、数据和版本信息声明为资源:

1 ICON "demo.ico"
7 RCDATA "payload.bin"
1 VERSIONINFO
FILEVERSION 1,2,3,4
PRODUCTVERSION 1,2,3,4
FILEOS 0x40004
FILETYPE 1
BEGIN
  BLOCK "StringFileInfo"
  BEGIN
    BLOCK "040904b0"
    BEGIN
      VALUE "FileDescription", "REA resource demo"
      VALUE "FileVersion", "1.2.3.4"
    END
  END
  BLOCK "VarFileInfo"
  BEGIN
    VALUE "Translation", 0x409, 1200
  END
END

编译资源对象并链接到 Windows PE。这两条命令只生成文件,不会执行它:

zig rc /:auto-includes none /:output-format coff -- demo.rc demo.res.o
zig cc -target x86_64-windows-gnu main.c demo.res.o -o demo.exe
file demo.exe

我们的 file 输出显示它是 PE32+ x86-64 控制台程序;REA 也将其识别为 PE32+。

查看资源树

安装 REA 并使用受支持的 Node 版本后,运行:

rea inspect-pe-resources demo.exe --format json > rea-resources.json

查看 normalized_result.resources。每一项都有类型、名称、语言,以及资源数据的 RVA、文件偏移、长度和 SHA-256。我们的构建得到四项,coverage.status 为 "complete":

类型 ID 名称 ID 语言 ID 数据长度 含义
3 1 1033 82 字节 RT_ICON 图像数据
10 7 1033 27 字节 RT_RCDATA,即我们放入的数据
14 1 1033 20 字节 RT_GROUP_ICON 图标组目录
16 1 1033 344 字节 RT_VERSION 版本资源

语言 ID 1033 即 0x0409。它是文件中记录的标识,并不能证明 Windows 最终会选择哪种语言。REA 遇到具名资源时也会保留名称,因此不要假定所有资源都只有数字 ID。

理解图标组和图像的关系

资源编译器没有把整个 .ico 原样存成一项,而是把我们的输入拆成图标图像和图标组目录。REA 在 normalized_result.icon_groups 中报告一个 16×16、32 位条目,resource_id 为 1;candidate_resource_indices 包含 0,same_language_resource_index 为 0,size_matches 为 true。

这些字段描述的是文件里的静态关联,不能证明 Windows 运行时选中了哪个图标、图像能否解码,或者资源管理器最终显示了什么。需要确认显示效果时,请在实际 Windows 环境中测试。

核对数据,并用其他工具读取版本

我们这次构建的 RCDATA 条目,payload.location 是偏移 220064、长度 27;SHA-256 为:

574cc5c002054f36046755069ea9433cbe0d363054a2a0d3ca54f64a506d68b6

运行 shasum -a 256 payload.bin 得到相同结果。还可以直接核对本次构建的文件区间:dd if=demo.exe bs=1 skip=220064 count=27 2>/dev/null | shasum -a 256。换一个构建时,应使用该文件自己的 REA 结果中的偏移和长度。

要用独立工具复核,可以在 Python 虚拟环境中安装 pefile==2024.8.26,然后运行:

import hashlib, pefile
p = pefile.PE('demo.exe')
for t in p.DIRECTORY_ENTRY_RESOURCE.entries:
    for n in t.directory.entries:
        for lang in n.directory.entries:
            data = lang.data.struct
            offset = p.get_offset_from_rva(data.OffsetToData)
            payload = p.__data__[offset:offset + data.Size]
            print(t.id, n.id, lang.id, offset, data.Size,
                  hashlib.sha256(payload).hexdigest())
for version in p.VS_FIXEDFILEINFO:
    print('version', version.FileVersionMS >> 16, version.FileVersionMS & 65535,
          version.FileVersionLS >> 16, version.FileVersionLS & 65535)

本次 pefile 与 REA 对四个资源的偏移、长度和哈希全部一致;pefile 还读出固定文件版本 1 2 3 4。这个版本号来自 pefile 的独立检查;REA 6.8.0 的资源清单把版本资源内容作为不透明数据处理。

如果清单无法完成

REA 的检查有调用者可设置的限制。例如,故意把目录条目预算设得很小:

rea inspect-pe-resources demo.exe --max-entries 1 --format json

我们的运行返回 resource_constraint,说明目录条目预算已耗尽,没有返回部分清单。确有需要时再提高限制;检查失败或没有结果,并不等于文件里没有资源。

这里的结论只针对这个文件的静态内容,不能证明发布者身份、签名有效性、运行行为或程序安全。若下一步想确认 PE 的 32 位或 64 位架构,可以继续阅读 检查 EXE 和 DLL 的架构。