SuperAPI AI Directory

re-blockchain

Data & analytics

View repository

Updated 10-15 21:34

About this skill

EVM smart contract reverse engineering (cast / panoramix / web3.py)

When to use / when not to use

  • Use: when you have contract bytecode (on-chain address or .bin/.hex file) and need to recover the logic—ABI recovery, decompilation, storage layout reconstruction
  • Use: contract vulnerability analysis (reentrancy/integer overflow/permissions) or audit scoping before exploit development
  • Use: forensics on malicious contracts/token scam contracts (on-chain data acquisition, read-only pulls)
  • Don't use: when Solidity source code is available and unobfuscated—reading the source directly is more accurate (audit approach same as [[re-vuln]]); this skill targets no-source/bytecode-only
  • Don't use: non-EVM chains not covered in this section (e.g., WASM-based chains, different instruction set); for WASM go to [[re-wasm]]
  • Note: on-chain interaction is primarily read-only (cast call/cast code/cast storage); any chain writes/deployment/dynamic verification default to local anvil or an isolated network ([[re-analyze/platform-tips]] sandbox highest principle); contract analysis is basically static, follow "static first"

Tool preparation

python3 — the foundation for scripted analysis

  • Linux: apt install python3 / dnf install python3 / pacman -S python
  • macOS: brew install python3;Windows: choco install python
  • Verify: python3 --version(all scripts in this skill are Python 3)

cast (foundry) — the workhorse for on-chain interaction/bytecode/storage

  • Official installer (all platforms, installs to ~/.foundry/bin, includes forge/cast/anvil/chisel): curl -L https://foundry.paradigm.xyz | bash && foundryup
  • macOS: brew install foundry(official Homebrew formula, includes all four tools)
  • Arch: official reponone foundry—use AUR foundry-bin(yay -S foundry-bin etc.);Note pacman -S foundry is an unrelated GNOME Builder toolkit, don't install the wrong one; Debian/Ubuntu/Fedora have no official package—use the official installer
  • Verify: cast --version && anvil --version

web3.py (pip, Python >=3.10) — scripted on-chain analysis

  • pip install web3(official PyPI; current 8.x requires Python >=3.10,PyPI requires-python to be <4,>=3.10—only 7.x supports 3.8. Python 3.8/3.9 environments need to pin 7.x)
  • Verify: python3 -c "import web3; print(web3.__version__)"

panoramix (pip, Python 3.9–3.11) — EVM decompiler (disasm→control flow)

  • pip install panoramix-decompiler(official PyPI 0.6.x, requires Python 3.9–3.11); the more actively maintained fork is panoramix-decompiler-abi, both provide the panoramix command
  • Verify: panoramix --help(or python3 -m panoramix --help)
  • Usage: pass raw bytecode directly panoramix <hex> --abi to decompile; to pass a contract address you need the WEB3_PROVIDER_URI environment variable (pulls bytecode from the chain); outputs pseudocode .pan/disassembly .asm written to the cache directory

pyevmasm (pip, Python 3) — EVM disassembly

  • pip install pyevmasm(official PyPI); verify: pyevmasm -d 0x6001600101 outputs instruction lines
  • Alternative: cast disassemble <hex>(bundled with foundry, same purpose)

hevm / echidna (optional, formal/fuzz verification)

  • echidna: GitHub releases precompiled binaries (crytic/echidna) or cargo install --locked echidna(no distro packages in official repos; Arch has an AUR package); verify: echidna --version
  • hevm: GitHub releases (ethereum/hevm) or cargo build; verify: hevm --version
  • Both optional—finish the static analysis in steps 1-5 first, then bring these in if behavior verification is needed

solc / solc-select (optional) — storage layout cross-reference

  • pip install solc-select(Python 3)+ solc-select install 0.8.28(official PyPI/compiled artifact download); or official solc-bin binaries (Linux x86 static package)
  • Verify: solc --version
  • Not needed for source-less contracts; used when "obtaining the compiled artifact of the same source" to compare against the authoritative storage layout (Pitfall 2)

Procedure

Execute in order; archive each step's artifacts (bytecode/selector table/pseudocode/asm) with sha256 + path ([[re-ioc]] evidence chain).

  1. Contract bytecode acquisition (on-chain/file):

    # 链上(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
    
    • Distinguish creation code(includes initcode + runtime, the front part is deployment logic) from runtime code(pure business logic, first instruction is often 0x6080... PUSH1 0x80 to allocate memory)——Etherscan's Bytecode page is creation, Deployed Bytecode page is runtime; use runtime to analyze business logic
    • On-chain data acquisition discipline:cast call/cast code/cast storage read-only is safe; write-chain/transaction-type operations default to starting a local anvil test chain
  2. ABI recovery (function selectors/signatures):

    # 从字节码提取 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)"
    
    • Event topics are analogous: log topic0 (first 32 bytes) = keccak256(event signature), query etherface/4byte
    • When there are many candidate signatures, disambiguate using parameter length (CALLDATASIZE comparison), fallback logic, storage access patterns (Pitfall 1)
  3. Decompilation (panoramix approach: disasm→flow):

    panoramix "$(cat code.hex)" --abi > contract.pan    # 伪码(可读性优先)
    cast disassemble "$(cat code.hex)" > contract.asm   # 精确指令流(交叉验证真值)
    # 按地址反编译(需节点): WEB3_PROVIDER_URI=<rpc> panoramix 0xDEAD...
    
    • Workflow: disasm (instruction-level ground truth) → function boundary location (dispatch table:PUSH4 sel EQ PUSH2 off JUMPI pattern, located in the ~0x00-0x1f region at the start of the bytecode) → rough pseudocode read → return to asm for close reading of key functions
    • Rough output is the norm (Pitfall 4); all decompilation conclusions must be cross-verified with asm
  4. Logic recovery (storage layout/state variables):

    # 链上存储读取(状态变量从 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
    
    • Layout rules: inheritance is merged according to C3 linearization without changing the layout, each variable is 32 bytes (bool/uint8/address etc. can be packed in the same slot), dynamic arrays slot = keccak256(slot_index)、mapping slot = keccak256(key ++ slot_index); when source is available, solc --storage-layout outputs authoritative JSON for comparison
    • Cross-verify with ABI getters: public state variables automatically generate getters,cast call infer variable types and slots from the result
  5. Vulnerability analysis (reentrancy/integer overflow/permissions):

    grep -cE 'CALL|DELEGATECALL|CALLCODE' contract.asm     # 外部调用面(重入/交互边界)
    grep -E 'ADD|MUL|SUB' contract.asm | head               # 算术运算(<0.8 无内置溢出检查)
    grep -E 'CALLER' contract.asm | head                    # 调用者检查(onlyOwner 权限)
    
    • Reentrancy: state is modified only after an external call (CALL to a user-controllable address) (SSTORE order is after CALL)——check whether effects precede interactions
    • Overflow: Solidity <0.8 has no built-in checks——look at whether there is an overflow branch after ADD/MUL; for 0.8+ look for unchecked block
    • Permissions: CALLER compared against owner slot pattern; owner slot writable/unchecked means arbitrary calls (only initializing owner in the constructor is a common missing permission)
    • Optional behavior verification: echidna property testing (invariant/reentrancy/overflow patterns), hevm symbolic execution,anvil local reproduction of attack sequence
    • Conclusion discipline: vulnerability determination requires "path reachable" evidence (call chain + state conditions); instruction patterns alone are weak signals (Pitfall 4)

Non-EVM chains (Solana / Move)

The process is isomorphic to EVM (bytecode decompilation → vulnerability analysis), but the instruction set and resource model differ——first identify the runtime, then follow the corresponding path.

  • Solana (BPF bytecode): identifying features——ELF file + .text section is BPF instructions (eBPF-like instruction set); decompilation path——llvm-objdump -d(BPF disassembly) or a dedicated decompiler; analysis focus—program accounts and instruction scheduling (CPI cross-program invocation chains), account data layout (struct offsets)
  • Sui / Aptos (Move bytecode): identification features—module/resource/function table structure; decompilation—move disassembler (move disassemble); analysis focus—the impact of the resource model (objects/abilities) on the vulnerability surface (transfer/ownership logic), module dependency graph
  • DeFi/NFT scenarios: pool contracts (AMM constant product formula), token standard variants (SPL, etc.), locating authorization and withdrawal logic
  • Vulnerability analysis continuation: for logic vulnerabilities (reentrancy/permissions) follow the EVM approach; make separate judgments where the resource/account model differs

Cross-domain federation

  • [[re-managed]]: after this gateway's "identify runtime" identifies EVM bytecode (.bin/hex), it fixedly invokes this skill (contract bytecode is the "managed bytecode" branch)
  • [[re-vuln]]: interface with the contract vulnerability and vulnerability discovery gateway—echidna's coverage-guided/corpus ideas are the same as [[re-fuzzing]], and assertion crash analysis ideas are the same as [[re-crash-triage]]; the vulnerability report structure is consistent (entry/conditions/impact/fix)
  • On-chain data retrieval: cast/web3.py read-only via RPC (public RPC, self-owned node, Etherscan API); writing to chain and dynamic verification default to local anvil (isolated network)
  • [[re-wasm]]: non-EVM Wasm contracts/on-chain Wasm modules (same domain, different instruction set)
  • Reference [[re-analyze/platform-tips]] for static-first (large samples) and sandbox isolation branch (writing to chain/dynamic verification isolation)

Common pitfalls and traps

  • Selector collision: symptom—cast 4byte returns multiple candidate signatures, or parsing by a guessed signature cast call fails/is misaligned; cause—the selector is the first 4 bytes of keccak256(signature), collisions exist (malicious variants/known collision cases), and a large number of signatures are not in the database; countermeasures—cast sig holds only for canonical signatures (no spaces between parameters,uint256 do not write uint, addresses lowercase and complete), disambiguate candidate signatures using CALLDATASIZE comparison (parameter-length dispatch), fallback logic (fallback handling), storage access patterns; finally confirm by keccak256 comparison of the full signature
  • Storage layout vs source mapping is difficult: symptom—for contracts without source code, inference by "declaration order = slot order" is wrong, and the types of values read do not match/mix slots; cause—the layout is affected by compiler version, optimization flags, inheritance order (C3), and struct/array packing rules, and without source code there is no authoritative mapping; countermeasures—with source code: solc --storage-layout output JSON for comparison (compiler version must match); without source code: infer types from the SSTORE constants in the decompilation + getter behavior + value size, mark confidence and do not guess hard; compute dynamic array/mapping slots by the keccak256 formula (step 4), do not add one linearly
  • Proxy contract (delegatecall logic is elsewhere): symptom—the main contract's decompilation has only fallback + delegatecall forwarding, and no business logic can be found; cause—the upgradeable proxy pattern (EIP-1967, etc.) puts the logic in the implementation contract: the main contract only stores the implementation address and delegatecalls, state variable layout is in the main contract, code is in the implementation contract (layout conflicts are themselves a classic vulnerability); countermeasures—first check the EIP-1967 implementation slot: cast storage <addr> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url <rpc>(admin slot 0xb53127684a568b3173ae13bae8d486943e0793e1d5dccd29778a66c42e1c39d), for non-standard proxies locate the SLOAD constant slot from the decompilation to read the implementation address; then cast code <实现地址> Analyze the implementation; implementation address is mutable = upgradeable, annotate the conclusion with the point in time at which it was read
  • Decompiler tool output is rough and requires manual work: Phenomenon — panoramix pseudocode lacks variable names/types/storage semantics, function boundaries are misaligned, leading to the mistaken belief that the contract logic is chaotic or that key branches are missing; Cause — the EVM has no types and no symbol table, and symbolic execution/heuristic decompilation loses information, while modern Solidity optimized outputs (ABIEncoderV2, optimizer) have complex structures; Countermeasure — treat pseudocode only as a "rough reading map", and for key functions return to cast disassemble instruction-level close reading (dispatch table location → instruction-by-instruction semantics → storage/call patterns); cross-check with multiple tools (panoramix + cast disasm + echidna behavioral verification); annotate conclusions with confidence level and verification method