rea-reverse

.NET 程序集元数据与 IL 查看器

打开 .NET 的 DLL 或 EXE,查看程序集身份、目标框架、引用、全部命名空间、类型和成员、P/Invoke 导入与资源;点击方法即可反汇编它的 IL。全程不执行程序。

程序集不会离开你的浏览器. 元数据和 IL 都在一个禁止联网的 Web Worker 中本地读取,不上传任何内容。 如何自己验证

在单独页面打开工具(无广告、无统计)

.NET 程序集本身是一个 PE 文件,和任何 Windows 的 .exe、.dll 用的是同一种容器格式,但它携带的结构信息比原生二进制丰富得多。里面有一个元数据数据库:每个类型、方法、字段都有名字;依赖了哪些其他程序集、哪些原生库,都有记录;每个方法体以 CIL(通用中间语言)指令的形式保存,而这些指令引用的仍然是那些名字。所以托管程序集的大部分内容,不用运行、也不用反编译器,就能看明白。

这个工具在你的浏览器里读取这些结构。选择一个 .dll 或 .exe,你会看到程序集身份、引用列表、可以逐层展开的类型树、P/Invoke 声明和资源;点击某个方法,页面就反汇编它的 IL。文件自始至终不会上传。

使用步骤

  1. 准备一个托管的 .dll 或 .exe:可以来自构建输出的 bin 目录,也可以来自 .nupkg 里的 lib/<framework>/ 目录(NuGet 包就是一个 ZIP 文件)。
  2. 把它拖到上方的拖放区,或者点“选择文件”。元数据在浏览器里解析,文件不会上传。
  3. 先看“程序集”一节(名称、版本、公钥 token、目标框架),再看引用。在“搜索类型和成员”里输入名字,点击某个方法即可看到它的 IL。

真实输出示例

我们把 Newtonsoft.Json 13.0.3 的 netstandard2.0 版本拖进本页,复制程序集概览,再搜索 DeserializeObject、点击第一个方法得到它的 IL。排版做了压缩(标签和值放在同一行,省去类型树),所有值都原样保留。

样本: nuget.org 上 newtonsoft.json.13.0.3.nupkg 包里的 lib/netstandard2.0/Newtonsoft.Json.dll(MIT 许可,Copyright (c) 2007 James Newton-King),SHA-256 8c1dd5c1…6b4d80 (www.nuget.org/packages/Newtonsoft.Json/13.0.3)

程序集
名称          Newtonsoft.Json
版本          13.0.0.0
区域性        中性
公钥 token    30ad4fe6b2a6aeed
目标框架      .NETStandard,Version=v2.0
模块          Newtonsoft.Json.dll
MVID          af9ee0c5-a8cb-435a-8fc7-7f3f1c1a3657
元数据版本    v4.0.30319 (CLI 2.5)
PE            PE32 x86, 688.8 KB
类型          类库(DLL)
CLI 标志      ILONLY STRONGNAMESIGNED
入口点        —
内容          496 个类型,4185 个方法,2196 个字段

引用的程序集 (1)
netstandard  2.0.0.0  中性  cc7b13ffcd2ddd51

P/Invoke(DllImport) (0)
无。

8 个结果
public static object DeserializeObject(string value) 0x06000066 rva 0x2b40 — Newtonsoft.Json.JsonConvert
…

// Newtonsoft.Json.JsonConvert.DeserializeObject
// object Newtonsoft.Json.JsonConvert::DeserializeObject(string)
// token 0x06000066, tiny header, 9 bytes

.maxstack 8

IL_0000: ldarg.0
IL_0001: ldnull
IL_0002: ldnull
IL_0003: call       object Newtonsoft.Json.JsonConvert::DeserializeObject(string, System.Type, Newtonsoft.Json.JsonSerializerSettings)
IL_0008: ret

能做什么

输入。 .NET 程序集(.dll 或 .exe),最大 256 MiB,.NET Framework、.NET Core、.NET 5 及以后版本、.NET Standard 都可以。每次请求(生成报告,或者反汇编一个方法的 IL)超时时间 60 秒。

报告。 页面会展示:

  • 程序集身份:名称、版本、culture 和公钥标记(public key token,按运行时的算法从公钥计算得出)。目标框架取自程序集级别的 TargetFrameworkAttribute,例如 .NETCoreApp,Version=v8.0。模块名和 MVID 也会列出。
  • 引用:每个被引用的程序集及其版本、culture、公钥标记,以及 ModuleRef 表里点名的原生模块。
  • 类型树:先按命名空间,再按类型,嵌套类型挂在外层类型下面。每个类型显示种类(class、interface、struct、enum、delegate 或 static class)、可见性、是否 abstract、sealed,以及基类。每个字段显示类型、可见性,以及是否 static、const、readonly。每个方法显示 C# 风格的签名,例如 int Parse(string text, ref int position),还有可见性、是否 static、virtual、abstract、P/Invoke,以及元数据 token 和 RVA。
  • P/Invoke:每个 DllImport 声明,包括托管方法、原生 DLL、入口点名称、字符集、调用约定和 SetLastError。这是托管代码和原生代码的边界,往往是整份报告里最值得看的一张表。
  • 资源:清单资源(manifest resource)及其可见性。嵌入式资源显示大小和文件偏移,以 .resources 魔数 0xBEEFCACE 开头的会单独标出;链接式资源显示存放它的文件或程序集。
  • PE 与 CLI 头:机器类型(x86、x64、ARM64 等,也能识别 ReadyToRun 映像在 Linux、macOS 上使用的带操作系统标记的值)、PE32 还是 PE32+、DLL 还是可执行文件、节表、CLI 运行时版本、CLI 标志(ILONLY、32BITREQUIRED、STRONGNAMESIGNED 等)和入口点 token。
  • 元数据布局:元数据版本字符串(例如 v4.0.30319)、各个流(stream),以及每张元数据表的行数。
  • 映像标记:映像里在 IL 之外还带有预编译的原生代码时,标为 ReadyToRun;ILONLY 标志未设置时(例如含原生代码的 C++/CLI 程序集),标为混合模式。
  • 警告:某一行或某个签名解不出来时,记为一条警告(最多 200 条),而不是让整个分析失败。

按需查看 IL。 点击一个方法就反汇编它的方法体。你会看到方法头格式(tiny 或 fat)、maxStack、局部变量是否零初始化、代码长度、局部变量的类型,以及带 IL_xxxx 偏移的指令清单。操作数会被解析成可读的形式:调用和字段访问显示成员名,ldstr 显示字符串字面量,分支和 switch 显示跳转目标标签。异常处理子句(带捕获类型的 catch、filter、finally、fault)连同 try 区间和 handler 区间一起列出。没有 IL 的方法会说明原因:abstract、P/Invoke、由运行时实现,或者是原生代码。

原理

解析器遵循定义 .NET 元数据的 ECMA-335 标准。它读取 PE 头,在第 14 个数据目录里找到 CLI 头,通过节表把 RVA 映射到文件偏移,然后打开元数据根(BSJB 签名)。接着读取表流(#~,或者未压缩的 #-)和各个堆:#Strings 存名字,#US 存字符串字面量,#GUID 和 #Blob 存签名和特性参数。类型名和签名渲染成类似 C# 的文本,泛型参数使用声明时的名字。

PE 读取、元数据布局、签名语法、操作码表和方法体解析,移植自 REA 6.4.0(morluto 的开源项目,MIT 许可),并且用 REA 6.4.0 在同一批样例程序集上的输出做了对照测试。

IL 部分:页面读取方法 RVA 处的方法头,用完整的 CIL 操作码表逐条解码指令,把每个元数据 token 解析成可读的名字,再解析 fat 方法体后面的异常处理段。只有你点击的那个方法才会被解码,所以大程序集不需要一开始就整体反汇编。

如果文件没有 CLI 头,页面会检查 NativeAOT 的特征(名为 .managed 或 hydrated 的节,或者文件中任意位置出现的 DotNetRuntimeDebugHeader 字符串),然后告诉你这是 NativeAOT 编译的程序,还是普通的原生 PE。

隐私是怎么保证的

程序集不会被上传:

  • 查看器运行在一个单独的框架(iframe)里,来源是 tools.rea-reverse.com。下面说的处理都在这个框架里、在你的设备上完成。
  • 这个页面在正文之后展示一个广告。广告脚本运行在 rea-reverse.com 上,而框架属于另一个源(origin),浏览器不允许广告脚本或本页的任何其他脚本读取你拖入的程序集或结果。
  • 框架的内容安全策略拦截它的脚本能发起的所有请求(connect-src 'none',也不加载任何第三方资源)。框架不能自行打开窗口或让本页跳转;只有你点击报告里的链接时才会打开页面。它传给本页的只有自身高度和下面这些统计事件。
  • 分析和每一次 IL 请求都在你设备上的 Web Worker 里进行。Worker 通过浏览器的文件 API 读取文件,只把报告或指令清单发回工具。
  • 统计事件只有粗粒度信息:成功或错误码、类型数量区间(例如 11-100)、文件大小区间、耗时区间,绝不包含文件名、类型名、方法名或任何其他内容。框架把事件交给本页,由本页发送。完整清单见隐私政策。
  • 不想要任何广告和统计?在单独页面打开查看器。

如何自己验证

  1. 打开浏览器开发者工具的 Network(网络) 面板,加载本页。发往 tools.rea-reverse.com 的请求就是框架:它的 HTML、样式、字体和脚本,以及 Worker 脚本 /_astro/dotnet-worker-*.js。发往 Google 域名的请求是本页的广告。
  2. 清空日志,然后分析一个程序集,再打开几个方法。在过滤框里输入 domain:tools.rea-reverse.com:最多只会再出现一次 Worker 脚本 /_astro/dotnet-worker-*.js(为下一次运行准备的新 Worker),没有任何请求带着你的数据。广告请求可能还会继续,但它们来自本页,不带框架里的任何内容。
  3. 点开来自 tools.rea-reverse.com 的 dotnet-metadata/ 文档,查看 Headers:它的 Content-Security-Policy 里有 connect-src 'none' 和 frame-ancestors https://rea-reverse.com。在 Chrome 里,Application(应用)> Frames 也能看到框架的源和策略。Worker 运行在 default-src 'none'; script-src 'self'; connect-src 'none' 这条策略下,浏览器不允许它发出任何网络请求。
  4. 检查隔离:在 Console(控制台) 里,上下文选 top,运行 document.getElementById('tool-frame').contentDocument,结果是 null;frames[0].document 会抛出 SecurityError。广告脚本拿到的也是这个结果。
  5. 或者先打开页面再断网(关掉 Wi-Fi,或在 Network 面板勾选 Offline),然后分析文件,照样能用。

局限

  • 不反编译成 C#。 工具展示的是元数据和 IL,不会还原 C# 或任何其他源码。签名写成 C# 的样子,只是为了好读。
  • NativeAOT 和原生 PE 没有 IL。 NativeAOT 把所有代码编译成机器码,不保留 ECMA-335 元数据,所以这里没有东西可看。请改用 Ghidra 这类原生反汇编或反编译工具。
  • 不展示 ReadyToRun 的原生代码。 对 ReadyToRun 映像,页面展示 IL 和元数据,不展示旁边那份预编译的机器码。混合模式程序集同样只展示托管部分。
  • 单文件发布要先解包。 单文件发布的应用最外层是一个原生宿主程序,没有 CLI 头。请分析解包出来的程序集。
  • 混淆过的名字仍然是混淆的。 名字按原样显示,超过 1024 个字符会被截断。
  • 不验证签名。 STRONGNAMESIGNED 只是头部的一个标志,页面不验证强名称签名,也不验证 Authenticode 签名。
  • 单个方法的上限。 每个方法最多解码 1 MiB 的 IL、20 万条指令。遇到未知操作码或操作数越界时停止解码,并给出说明。
  • 只做静态分析。 不执行任何代码,所以反射、动态加载、运行时生成的代码都看不到。

合规说明

请只分析你自己构建的程序集、你获得授权检查的程序集,或者开源程序集。请遵守所分析软件的许可协议(包括其中限制逆向工程的条款)以及适用于你的法律法规。本工具不提供任何绕过授权或其他保护措施的建议。另见免责声明。

对应的命令行

file 就能区分 .NET 程序集和原生 DLL(macOS 自带的 file 5.41):

$ file Newtonsoft.Json.dll
Newtonsoft.Json.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows

PE 文件之所以是 .NET 程序集,是因为 CLR Runtime Header 这个数据目录不为零(LLVM objdump,Apple LLVM 21.0.0):

$ objdump -p Newtonsoft.Json.dll | grep -E 'Magic|CLR Runtime'
Magic                   010b    (PE32)
Entry e 00002008 00000048 CLR Runtime Header

目标框架是 TargetFrameworkAttribute 的一个字符串参数,所以 strings 也能找到它,只是看不出它属于哪个特性:

$ strings -a Newtonsoft.Json.dll | grep -F 'Version=v'
.NETStandard,Version=v2.0

常见问题

有没有不用上传文件的在线 .NET DLL 查看器?

就是本页。程序集在你浏览器里的 Web Worker 中解析,不会上传;有些反编译网站则要把 DLL 发到服务器上。打开浏览器开发者工具的 Network 面板就能确认。

怎么查看一个 DLL 的 .NET 版本?

看“程序集”一节里的“目标框架”:它取自程序集的 TargetFrameworkAttribute,比如上面 Newtonsoft.Json 的 .NETStandard,Version=v2.0。“元数据版本”(v4.0.30319)是元数据格式的版本,几乎所有程序集都一样,看不出目标框架。

这是在线 DLL 反编译器吗?

不是。它像 ILDasm 一样展示元数据和 IL,不还原 C#。需要 C# 源码时,请在自己电脑上用 ILSpy、dnSpyEx 或 dotPeek 这类反编译器。

怎么判断一个 EXE 是 .NET 还是原生程序?

拖进来看:.NET 程序集会显示“程序集”一节;没有 CLI 头的文件会被报告为原生 PE,带有 NativeAOT 特征时还会注明 NativeAOT。在命令行里,file 对托管文件会打印 Mono/.Net assembly。

为什么某个方法显示没有 IL?

它是 abstract 方法、P/Invoke 声明、由运行时实现的方法(例如委托的 Invoke),或者是混合模式程序集里的原生代码。

为什么公钥 token 是空的?

这个程序集或这条引用没有使用强名称。Newtonsoft.Json 使用了强名称,所以显示它的 token 30ad4fe6b2a6aeed。

更新于

相关教程