rea-reverse

EVM 合约函数选择器提取:从字节码读出选择器、参数和存储布局

粘贴已部署合约的 runtime 字节码,列出全部 4 字节函数选择器,以及推断出的参数类型、状态可变性、存储布局和编译器元数据。不需要 ABI、验证过的源码,也不需要 RPC。

字节码不会离开你的浏览器. EVMole 以 WebAssembly 形式在本页的 Web Worker 中运行,不上传任何内容。 如何自己验证

空白和开头的 0x 会被忽略。字节码上限 1 MiB。

十六进制文本或原始字节码均可。

使用前请注意两点局限

  • 状态可变性(mutability)是从字节码推断出来的,可能与源码中的声明不同,例如没有读取状态的 view 函数会被推断成 pure。
  • 暂不支持 EOF 合约(以 0xEF00 开头的字节码),只分析传统(legacy)EVM 字节码。

部署到链上的智能合约并不携带 ABI,它携带的只有字节码。但几乎所有 Solidity 或 Vyper 合约的字节码开头都有一段分发器(dispatcher):它把调用数据的前 4 个字节和合约支持的各个外部函数的选择器逐一比较,然后跳转到对应的函数体。所以,即使没有源码,也能从字节码里把这些选择器,以及每个函数的不少信息读出来。

这个工具就在你的浏览器里做这件事。粘贴 runtime 字节码或者选择一个文件,页面会列出它找到的选择器,以及能推断出的参数类型、状态可变性和存储布局。字节码自始至终不会离开你的电脑。

能做什么

输入。 在文本框里粘贴十六进制的 runtime 字节码。0x 前缀可带可不带,空白字符(空格、换行)会被忽略,所以复制来的整块内容直接粘贴就行。也可以选择一个文件,文件内容是十六进制文本或者原始字节都可以,页面会自动处理。上限:字节码最多 1 MiB,输入最多 4 MiB,分析超时时间 30 秒。

输出。 对分析器找到的每个函数,你会看到:

  • 4 字节选择器,例如 0xa9059cbb
  • 函数体在字节码中的字节偏移
  • dispatch 类型,说明这个函数是怎样从分发器到达的
  • 推断的参数类型,例如 address,uint256
  • 推断的状态可变性:pure、view、payable 或 nonpayable

函数列表之外,报告还包括:

  • 存储布局:代码访问的存储槽(slot),每个槽的偏移(offset)和推断类型,以及哪些选择器读、哪些选择器写这个槽。transient storage(TLOAD/TSTORE)也包含在内。
  • 编译器 metadata:solc 等编译器附加在 runtime 代码末尾的 CBOR 数据块,能解码的会解码出来,常见内容是 solc 版本号和 metadata 文件的 IPFS 哈希。

整个结果可以导出为 JSON,方便留档、和以后的版本做对比,或者交给其他工具继续处理。

函数名从哪里来

选择器是函数签名的 keccak256 哈希的前 4 个字节,例如 keccak256("transfer(address,uint256)")。哈希不可逆,所以函数名不可能从字节码里“解码”出来,唯一的办法是拿选择器去已知签名表里查。

页面内置了一份离线签名表,收录两百多个常见签名:ERC-20、ERC-721、ERC-1155、Ownable、AccessControl、Uniswap 等常用接口。工具不会查询 4byte.directory 或任何在线数据库。在表里查到的选择器标为离线库匹配,查不到的标为未知。如果签名表里的参数和从字节码推断出的参数对不上,页面会提示这可能是选择器碰撞或者推断偏差。4 字节选择器确实会碰撞,所以名字匹配只是一个假设,不是证明。

原理

分析引擎是 EVMole 0.9.3,编译成 WebAssembly,在 Web Worker 中运行,因此页面不会卡住。EVMole 不需要源码,也不需要 ABI。它对分发器做符号执行,找出选择器比较和对应的跳转目标;然后沿着每个函数体继续分析:函数怎样读取调用数据(据此推断参数类型)、是否检查 CALLVALUE(据此判断是否 payable)、会执行到哪些读写存储和改变状态的操作码(据此判断 view、pure 还是 nonpayable)。对 SLOAD、SSTORE、TLOAD、TSTORE 的同一套追踪,就得到了存储布局。

metadata 解码则是读取代码最后两个字节表示的长度,再解析它前面的 CBOR 映射,solc 就是在这里写入 solc、ipfs 等字段的。

REA 命令行的 inspect-evm-interface 用的也是同一个 EVMole 版本,所以你可以离线用 REA 复核浏览器里的结果,见文末的相关教程。

隐私是怎么保证的

字节码不会被上传,这是页面的实现方式决定的:

  • 分析在你设备上的 Web Worker 里进行。EVMole 的 WebAssembly 文件(约 470 KB)在页面加载时就从本站 /_astro/ 路径下预先下载好了,所以之后即使断网,分析也照样能做。
  • Worker 运行在一条内容安全策略(CSP)之下,只允许向本站同源地址发请求,这是为了加载那个 .wasm 文件。Worker 发往其他任何地址的网络请求都会被浏览器拦截。
  • 唯一的统计事件是 evm_analyze,只包含成功或错误码、找到的函数数量区间、耗时区间,绝不包含字节码、选择器、函数名或任何其他内容。完整的事件清单见隐私政策。
  • 工具页不展示广告。

如何自己验证

  1. 在粘贴任何内容之前,打开浏览器开发者工具,切到 Network(网络) 面板。做一次分析,逐条查看出现的请求:没有任何一条带着你的字节码,本站的请求只有 .wasm 文件这类页面资源。
  2. 或者先打开本页,等它加载完成,然后断开网络(关闭 Wi-Fi,或在 Network 面板勾选 Offline),再做分析。分析照样能完成,因为没有任何东西需要离开你的电脑。

局限

请把输出当作“从字节码推断出的结果”来读,而不是经过验证的 ABI。

  • 状态可变性是从字节码推断的,可能和源码里的声明不同。 例如,一个声明为 view 但实际上没有读取任何状态的函数,可能被推断成 pure。编译器和优化器也会改变字节码里能看到的东西。
  • 暂不支持 EOF 合约。 以 0xEF00 开头的代码(EVM Object Format)会被拒绝。
  • 需要 runtime(已部署)字节码,而不是 creation/init 代码。creation 代码里有构造函数,它负责把 runtime 代码复制到内存再返回,它的开头并不是你要找的分发器。用 solc 时请用 --bin-runtime,不要用 --bin。
  • 代理合约只能看到代理自身的选择器。 代理会用 DELEGATECALL 把它不认识的调用转发给实现合约。要看真正的接口,请分析实现合约的字节码。
  • 参数类型是推断的。 简单类型通常是对的;嵌套结构体、数组等复杂或动态类型可能不准确。
  • 不同编译器效果不同。 Vyper 的产物或手写字节码,结果可能与常见的 solc 产物不同,有时不完整。
  • 不产出源码,也不反编译。 工具还原的是接口和存储布局,不是业务逻辑,不会生成 Solidity 或其他任何源码。
  • 列表为空或很短,不能证明没有别的可调用入口。 不常见的分发器写法可能让部分函数逃过分析。

常见问题

函数名会去网上查吗?

不会。函数名只来自页面内置的离线签名表,表里没有的选择器显示为“未知”。如果你自己去公开数据库查,请把查到的结果当作假设,因为选择器会碰撞。

源码里写的是 view,为什么显示 pure?

可变性是根据字节码实际做了什么推断出来的。如果一个 view 函数从不读取存储或环境数据,代码里就没有任何东西能把它和 pure 区分开。反过来的情况也可能出现。手上有 ABI 时,请以 ABI 为准对照。

粘贴了字节码,却几乎没有结果,为什么?

最常见的原因是:粘贴的是 creation 代码而不是 runtime 代码、这是一个代理合约,或者这是 EOF 合约(以 0xEF00 开头)。先确认你复制的是哪一种字节码。

runtime 字节码从哪里拿?

从你自己的构建产物里拿(solc --bin-runtime,或者 Hardhat、Foundry 产物里的 deployedBytecode 字段),或者对你有权分析的地址,用自己的节点调用 eth_getCode 获取。工具本身不发起任何 RPC 或区块浏览器请求。

这里的存储布局和 solc 输出的 storage layout 一样吗?

不一样。这里是根据代码实际访问的槽位重建出来的:能看到槽位、偏移、推断类型和哪些选择器访问了它,但看不到变量名;运行时才算出来的槽位(例如 mapping 的元素)不一定能完整呈现。

导出的 JSON 里有什么?

和页面上显示的内容一样:选择器、偏移、dispatch 类型、参数、可变性、名字匹配情况、存储布局和解码后的 metadata。JSON 在浏览器里生成,直接保存到你的设备上。

合规说明

请只用本工具分析你自己编写的合约、你获得授权审查的合约,或者字节码已经公开在链上的公开合约。请遵守所分析代码的许可协议以及适用于你的法律法规。还原出接口并不意味着你对这些代码享有任何权利,本工具也不提供任何攻击合约的建议。

相关教程