12 KiB
12 KiB
name, description, capabilities
| name | description | capabilities | |
|---|---|---|---|
| re-blockchain | EVM 智能合约逆向:字节码反编译、漏洞分析。 触发词:智能合约、EVM、Solidity、字节码、合约漏洞、ABI、Solana、Move、BPF |
|
EVM 智能合约逆向(cast / panoramix / web3.py)
何时使用 / 何时不用
- 用:拿到合约字节码(链上地址或 .bin/.hex 文件)要还原逻辑——ABI 恢复、反编译、存储布局重建
- 用:合约漏洞分析(重入/整数溢出/权限)或漏洞利用前的审计定位
- 用:恶意合约/代币诈骗合约取证(链上数据获取、只读拉取)
- 不用:Solidity 源码可得且未混淆——直接读源码更准(审计思路同 re-vuln),本技能面向无源码/仅字节码
- 不用:本节未覆盖的非 EVM 链(如 WASM 系链,指令集不同);WASM 走 re-wasm
- 注意:链上交互以只读为主(
cast call/cast code/cast storage);任何写链/部署/动态验证默认本地 anvil 或隔离网络(re-analyze/platform-tips 沙箱最高原则);合约分析基本静态,按「静态优先」执行
工具准备
python3 —— 脚本化分析基础
- Linux:
apt install python3/dnf install python3/pacman -S python - macOS:
brew install python3;Windows:choco install python - 验证:
python3 --version(本技能脚本均为 Python 3)
cast(foundry)—— 链上交互/字节码/存储主力
- 官方安装器(全平台,装到
~/.foundry/bin,含 forge/cast/anvil/chisel):curl -L https://foundry.paradigm.xyz | bash && foundryup - macOS:
brew install foundry(官方 Homebrew formula,含四件套) - Arch: 官方仓库无 foundry——用 AUR
foundry-bin(yay -S foundry-bin等);注意pacman -S foundry是无关的 GNOME Builder 工具包,别装错;Debian/Ubuntu/Fedora 无官方包——用官方安装器 - 验证:
cast --version && anvil --version
web3.py(pip,Python >=3.10)—— 脚本化链上分析
pip install web3(官方 PyPI;当前 8.x 要求 Python >=3.10,PyPIrequires-python为<4,>=3.10——7.x 才支持 3.8。Python 3.8/3.9 环境需 pin 7.x)- 验证:
python3 -c "import web3; print(web3.__version__)"
panoramix(pip,Python 3.9–3.11)—— EVM 反编译器(disasm→流程)
pip install panoramix-decompiler(官方 PyPI 0.6.x,要求 Python 3.9–3.11);维护更活跃的 fork 为panoramix-decompiler-abi,两者都提供panoramix命令- 验证:
panoramix --help(或python3 -m panoramix --help) - 用法: 直接传原始字节码
panoramix <hex> --abi反编译;传合约地址则需WEB3_PROVIDER_URI环境变量(从链上拉字节码);输出伪码.pan/反汇编.asm写到 cache 目录
pyevmasm(pip,Python 3)—— EVM 反汇编
pip install pyevmasm(官方 PyPI);验证:pyevmasm -d 0x6001600101输出指令行- 备选:
cast disassemble <hex>(foundry 自带,同一用途)
hevm / echidna(可选,形式化/模糊验证)
- echidna: GitHub releases 预编译二进制(crytic/echidna)或
cargo install --locked echidna(官方仓库无发行版包;Arch 有 AUR 包);验证:echidna --version - hevm: GitHub releases(ethereum/hevm)或 cargo 构建;验证:
hevm --version - 均为可选——先跑完步骤 1-5 的静态分析,需要行为验证再上
solc / solc-select(可选)—— 存储布局对照
pip install solc-select(Python 3)+solc-select install 0.8.28(官方 PyPI/编译产物下载);或 solc-bin 官方二进制(Linux x86 静态包)- 验证:
solc --version - 无源码合约不需要;用于"拿到同源码编译产物"时对照权威 storage layout(坑 2)
操作步骤
按顺序执行,每步产物(字节码/选择器表/伪码/asm)存档 sha256 + 路径(re-ioc 证据链)。
-
合约字节码获取(链上/文件):
# 链上(RPC 节点;公开 RPC 或自有节点) cast code 0xDEAD... --rpc-url https://eth.llamarpc.com > code.hex # 文件(.bin/.hex 文本或二进制) xxd -r -p code.hex > code.bin && file code.bin && xxd code.bin | head -2 sha256sum code.bin > code.sha256- 区分 creation code(含 initcode + runtime,前面是部署逻辑)与 runtime code(纯业务逻辑,首指令常
0x6080...PUSH1 0x80 分配内存)——Etherscan 的 Bytecode 页是 creation、Deployed Bytecode 页是 runtime;分析业务逻辑用 runtime - 链上数据获取纪律:
cast call/cast code/cast storage只读安全;写链/交易类操作默认本地anvil起测试链
- 区分 creation code(含 initcode + runtime,前面是部署逻辑)与 runtime code(纯业务逻辑,首指令常
-
ABI 恢复(函数选择器/签名):
# 从字节码提取 PUSH4 选择器常量(4 字节 = keccak256(signature)[:4]) cast disassemble "$(cat code.hex)" | grep -E 'PUSH4' | awk '{print $NF}' | sort -u > selectors.txt # 对照签名库(4byte.directory / etherface 在线服务) while read s; do echo -n "$s "; cast 4byte "$s"; done < selectors.txt # 已知签名本地计算(签名必须规范化,见坑 1) cast sig "transfer(address,uint256)" cast sig "balanceOf(address)"- 事件 topic 同理:日志 topic0(前 32 字节)= keccak256(事件签名),对 etherface/4byte 查询
- 候选签名多时用参数长度(CALLDATASIZE 比较)、回退逻辑、存储访问模式消歧(坑 1)
-
反编译(panoramix 思路:disasm→流程):
panoramix "$(cat code.hex)" --abi > contract.pan # 伪码(可读性优先) cast disassemble "$(cat code.hex)" > contract.asm # 精确指令流(交叉验证真值) # 按地址反编译(需节点): WEB3_PROVIDER_URI=<rpc> panoramix 0xDEAD...- 工作流:disasm(指令级真值)→ 函数边界定位(分派表:
PUSH4 sel EQ PUSH2 off JUMPI模式,位于字节码开头 ~0x00-0x1f 区域)→ 伪码粗读 → 关键函数回 asm 精读 - 输出粗糙是常态(坑 4),一切反编译结论以 asm 交叉验证
- 工作流:disasm(指令级真值)→ 函数边界定位(分派表:
-
逻辑还原(存储布局/状态变量):
# 链上存储读取(状态变量从 slot 0 起、每槽 32 字节) cast storage 0xDEAD... 0 --rpc-url <rpc> cast storage 0xDEAD... 1 --rpc-url <rpc> # 动态数组/映射槽位 = keccak256 派生(见坑 2 公式) cast call 0xDEAD... "owner()(address)" --rpc-url <rpc> # 公开变量 getter # 无源码:从反编译的 SSTORE/SLOAD 常量与 getter 推断语义 grep -E 'SSTORE|SLOAD' contract.asm | head- 布局规则:继承按 C3 线性化合并不变式、每变量 32 字节(
bool/uint8/address等可同槽打包)、动态数组slot = keccak256(slot_index)、mappingslot = keccak256(key ++ slot_index);有源码时solc --storage-layout出权威 JSON 对照 - 与 ABI getter 交叉验证:公开状态变量自动生成 getter,
cast call结果反推变量类型与槽位
- 布局规则:继承按 C3 线性化合并不变式、每变量 32 字节(
-
漏洞分析(重入/整数溢出/权限):
grep -cE 'CALL|DELEGATECALL|CALLCODE' contract.asm # 外部调用面(重入/交互边界) grep -E 'ADD|MUL|SUB' contract.asm | head # 算术运算(<0.8 无内置溢出检查) grep -E 'CALLER' contract.asm | head # 调用者检查(onlyOwner 权限)- 重入:外部调用(CALL 到用户可控地址)之后才改状态(SSTORE 顺序在 CALL 之后)——检查 effects 是否在 interactions 之前
- 溢出:Solidity <0.8 无内置检查——看 ADD/MUL 后是否有溢出分支;0.8+ 找
unchecked块 - 权限:CALLER 与 owner 槽比较模式;owner 槽可写/无检查即任意调用(只有构造函数初始化 owner 是常见权缺失)
- 可选行为验证:echidna 属性测试(不变量/重入/溢出模式)、hevm 符号执行、
anvil本地复现攻击序列 - 结论纪律:漏洞判定需"路径可达"证据(调用链 + 状态条件),仅指令模式是弱信号(坑 4)
非 EVM 链(Solana / Move)
流程与 EVM 同构(字节码反编译 → 漏洞分析),指令集与资源模型不同——先识别运行时,再按对应路径走。
- Solana(BPF 字节码):识别特征——ELF 文件 + .text 段为 BPF 指令(eBPF 类指令集);反编译路径——
llvm-objdump -d(BPF 反汇编)或专用反编译器;分析重点——程序账户与指令调度(CPI 跨程序调用链)、账户数据布局(结构体偏移) - Sui / Aptos(Move 字节码):识别特征——模块/资源/函数表结构;反编译——move disassembler(
move disassemble);分析重点——资源模型(对象/能力)对漏洞面的影响(转账/所有权逻辑)、模块依赖图 - DeFi/NFT 场景:池子合约(AMM 常量积公式)、代币标准变体(SPL 等)、授权与提现逻辑定位
- 漏洞分析承接:逻辑漏洞(重入/权限)沿用 EVM 思路,资源/账户模型差异处单独判断
跨域联合
- re-managed:本网关「识别运行时」识别到 EVM 字节码(.bin/hex)后固定调用本技能(合约字节码是"托管字节码"分支)
- re-vuln:合约漏洞与漏洞挖掘网关衔接——echidna 的覆盖引导/语料思路同 re-fuzzing,断言崩溃分析思路同 re-crash-triage;漏洞报告结构一致(入口/条件/影响/修复)
- 链上数据获取:cast/web3.py 经 RPC 只读(公开 RPC、自有节点、Etherscan API);写链与动态验证默认本地 anvil(隔离网络)
- re-wasm:非 EVM 的 Wasm 合约/链上 Wasm 模块(同域不同指令集)
- 引用 re-analyze/platform-tips 静态优先(大型样本)与沙箱隔离分支(写链/动态验证隔离)
常见坑与陷阱
- 选择器碰撞:现象——
cast 4byte返回多个候选签名,或按猜的签名cast call解析失败/结果错位;原因——选择器是 keccak256(signature) 前 4 字节,碰撞存在(恶意变体/已知碰撞案例),且大量签名未入库;对策——cast sig只对规范化签名成立(参数间无空格、uint256不写uint、地址小写完整),候选签名用 CALLDATASIZE 比较(参数长度分派)、回退逻辑(fallback 处理)、存储访问模式消歧;最终以完整签名 keccak256 比对确认 - 存储布局 vs 源码映射难:现象——无源码合约按"声明顺序=槽位顺序"推断出错,读出的值类型对不上/混槽;原因——布局受编译器版本、优化开关、继承顺序(C3)、结构体/数组打包规则影响,无源码时没有权威映射;对策——有源码:
solc --storage-layout出 JSON 对照(编译器版本要一致);无源码: 从反编译的 SSTORE 常量 + getter 行为 + 值大小推断类型,标注置信度不硬猜;动态数组/mapping 槽位按 keccak256 公式算(步骤 4),别线性加一 - 代理合约(delegatecall 逻辑在别处):现象——主合约反编译只有 fallback + delegatecall 转发,找不到任何业务逻辑;原因——升级代理模式(EIP-1967 等)把逻辑放实现合约:主合约只存实现地址并 delegatecall,状态变量布局在主合约、代码在实现合约(布局冲突本身是经典漏洞);对策——先查 EIP-1967 实现槽:
cast storage <addr> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url <rpc>(管理员槽0xb53127684a568b3173ae13bae8d486943e0793e1d5dccd29778a66c42e1c39d),非标准代理从反编译定位 SLOAD 常量槽位读实现地址;再cast code <实现地址>分析实现;实现地址可变=可升级,结论标注读取时点 - 反编译工具输出粗糙需手工:现象——panoramix 伪码缺变量名/类型/存储语义,函数边界错位,误以为合约逻辑混乱或遗漏关键分支;原因——EVM 无类型无符号表,符号执行/启发式反编译丢信息,现代 Solidity 优化产物(ABIEncoderV2、优化器)结构复杂;对策——伪码只当"粗读地图",关键函数回
cast disassemble指令级精读(分派表定位 → 逐指令语义 → 存储/调用模式);多工具交叉(panoramix + cast disasm + echidna 行为验证);结论标注置信度与验证方式