Erever
My swiss army knife for reversing EVM bytecodes (super experimental)
Install / Use
/learn @minaminao/EreverREADME
erever
erever is a CLI tool designed for reversing EVM bytecodes. erever is specially optimized for solving CTF challenges and is intended to be used for intricate operations. While erever is powerful for specific needs, for general reversing tasks, I recommend using other useful tools, such as the debugger included in Foundry. Currently, erever is very experimental and primarily developed for my personal problem-solving purposes. If you encounter any issues or have suggestions for improvements while using erever, please feel free to open an issue!
Table of Contents
Writeups with erever
- Paradigm CTF 2022: SOURCECODE
- DownUnderCTF 2022: EVM Vault Mechanism
- EKOPARTY CTF 2022: Byte
- EKOPARTY CTF 2022: SmartRev
- Numen Cyber CTF 2023: LittleMoney
- Numen Cyber CTF 2023: HEXP
- Paradigm CTF 2023: Cosmic Radiation
- Curta: Lana
Install
To install erever, run the following command:
uv tool install .
Note: Only supports Python >= 3.13.
Usage
erever has the following subcommands:
disassemble(alias:disas): Disassemble EVM bytecode into a more readable format.trace: Traces an execution for EVM bytecode.symbolic-trace(alias:symbolic): Performs symbolic execution tracing on EVM bytecode.mermaid: Generates a mermaid diagram to represent EVM bytecode structure visually.gadget: Searches for JOP (Jump-Oriented Programming) gadgets within EVM bytecode.assemble: Converts assembly instructions to EVM bytecode.
For detailed information on each command and its options, you can access the help message by running such commands:
erever -h
erever <COMMAND> -h
Disassemble
The disassemble command is used to break down EVM bytecode into its assembly language representation, making it easier to understand and analyze.
Usage:
erever disassemble <OPTIONS>
Let's walk through some examples of using the disassemble command.
First, we will disassemble the bytecode of a quine I used in the Paradigm CTF 2022 "SOURCECODE" challenge. The bytecode is compiled using the Huff language, and you can find the file at the following link: ctf-blockchain/src/ParadigmCTF2022/SourceCode/Quine35Bytes.huff at main · minaminao/ctf-blockchain.
To disassemble the bytecode, run the following command:
$ erever disassemble -b "70806011526000526070600e536023600ef3806011526000526070600e536023600ef3"
0x00: PUSH17 0x806011526000526070600e536023600ef3
0x12: DUP1
0x13: PUSH1 0x11
0x15: MSTORE
0x16: PUSH1 0x00
0x18: MSTORE
0x19: PUSH1 0x70
0x1b: PUSH1 0x0e
0x1d: MSTORE8
0x1e: PUSH1 0x23
0x20: PUSH1 0x0e
0x22: RETURN
The -b option specifies the byte code.
At the beginning of each line, the offset of the bytecode is displayed.
You can also use RPC to disassemble a bytecode on the blockchain. For example, if you want to disassemble the bytecode of Beacon Deposit Contract on the Ethereum mainnet, you can use the following command:
erever disas -c 0x00000000219ab540356cBB839Cbe05303d7705Fa --rpc-url $RPC_URL
If you do not pass --rpc-url on the command line, erever will automatically use the EREVER_RPC_URL environment variable if it is set. Example:
export EREVER_RPC_URL="YOUR_RPC_URL"
Also, you can look up function signatures using --analyze option:
$ erever disas --file blazctf_missing.toml --analyze | grep PUSH4
0x044: PUSH4 0x64d98f6e # isSolved()
0x136: PUSH4 0x43000817 # unknown
Trace
The trace command is used to trace the execution of EVM bytecode.
Usage:
erever trace <OPTIONS>
If you want to trace the execution of the bytecode of the quine we used in the previous example, you can use the following command:
$ erever trace -b "70806011526000526070600e536023600ef3806011526000526070600e536023600ef3"
0x00: PUSH17 0x806011526000526070600e536023600ef3
stack [0x806011526000526070600e536023600ef3]
0x12: DUP1(0x806011526000526070600e536023600ef3)
stack [0x806011526000526070600e536023600ef3, 0x806011526000526070600e536023600ef3]
0x13: PUSH1 0x11
stack [0x11, 0x806011526000526070600e536023600ef3, 0x806011526000526070600e536023600ef3]
0x15: MSTORE(offset:0x11, x:0x806011526000526070600e536023600ef3)
stack [0x806011526000526070600e536023600ef3]
memory 0000000000000000000000000000000000000000000000000000000000000000 | ................................ | 0x0
806011526000526070600e536023600ef3000000000000000000000000000000 | .`.R`.R`p`.S`#`................. | 0x20
0x16: PUSH1 0x00
stack [0x00, 0x806011526000526070600e536023600ef3]
0x18: MSTORE(offset:0x00, x:0x806011526000526070600e536023600ef3)
stack []
memory 000000000000000000000000000000806011526000526070600e536023600ef3 | ................`.R`.R`p`.S`#`.. | 0x0
806011526000526070600e536023600ef3000000000000000000000000000000 | .`.R`.R`p`.S`#`................. | 0x20
0x19: PUSH1 0x70
stack [0x70]
0x1b: PUSH1 0x0e
stack [0x0e, 0x70]
0x1d: MSTORE8(offset:0x0e, x:0x70)
stack []
memory 000000000000000000000000000070806011526000526070600e536023600ef3 | ..............p.`.R`.R`p`.S`#`.. | 0x0
806011526000526070600e536023600ef3000000000000000000000000000000 | .`.R`.R`p`.S`#`................. | 0x20
0x1e: PUSH1 0x23
stack [0x23]
0x20: PUSH1 0x0e
stack [0x0e, 0x23]
0x22: RETURN(offset:0x0e, size:0x23)
return 70806011526000526070600e536023600ef3806011526000526070600e536023600ef3
stack []
memory 000000000000000000000000000070806011526000526070600e536023600ef3 | ..............p.`.R`.R`p`.S`#`.. | 0x0
806011526000526070600e536023600ef3000000000000000000000000000000 | .`.R`.R`p`.S`#`................. | 0x20
By default, the stack is always displayed, and the memory is printed only at the time of writing.
If you want to always display the memory, use the --memory-display always option as follows:
erever trace -b "70806011526000526070600e536023600ef3806011526000526070600e536023600ef3" --memory-display always
If you don't want to display the memory, use the --memory-display off option.
Also, if you want to display only some parts of the memory, use the --memory-range option.
erever trace -b "70806011526000526070600e536023600ef3806011526000526070600e536023600ef3" --memory-display always --memory-range 0x20 0x40
Additionally, the context at runtime can be customized with options:
context options:
--address ADDRESS Address of the contract (default: 182267477)
--origin ORIGIN Origin of the transaction (default: 0)
--caller CALLER Caller of the transaction (default: 0)
--callvalue CALLVALUE Call value of the transaction (default: 0)
--calldata CALLDATA Call data of the transaction (default: )
--gasprice GASPRICE Gas price of the transaction (default: 0)
--coinbase COINBASE Coinbase of the block (default: 0)
--timestamp TIMESTAMP Timestamp of the block (default: 0)
--number NUMBER Number of the block (default: 0)
--difficulty DIFFICULTY Difficulty of the block (default: 0)
--gaslimit GASLIMIT Gas limit of the block (default: 0)
--chainid CHAINID Chain ID of the block (default: 1)
--selfbalance SELFBALANCE Balance of the contract (default: 0)
--basefee BASEFEE Base fee of the block (default: 0)
--gas GAS Gas of the transaction (default: (1 << 256) - 1)
Also, by using a TOML file, it is possible to overwrite some codes, balances, and storage values at runtime:
[state.code]
0x03f6296A2412CbC9B8239387f0ca0e94Ba2d6A99 = "0x60ff"
[state.balance]
0x03f6296A2412CbC9B8239387f0ca0e94Ba2d6A99 = "100"
[state.storage]
0xADD2E55 = { "0" = "0x34", "1" = "0x4142434445464748494a4b4c4d4e4f505152535455565758595a414243444546", "2" = "0x4748494a4b4c4d4e4f505152535455565758595a000000000000000000000000" }
You can also use RPC to trace the execution of a transaction.
For example, if you want to trace the execution of the contract creation transaction for Beacon Deposit Contract on the Ethereum mainnet, you can use the following command:
erever trace --tx 0xe75fb554e433e03763a1560646ee22dcb74e5274b34c5ad644e7c0f619a7e1d0 --rpc-url $RPC_URL
Note: Basically, erever interprets the number as hexadecimal if it is prefixed with 0x, and as decimal otherwise.
Symbolic Trace
The symbolic-trace command is used to perform symbolic execution tracing on EVM bytecode. This feature is experimental.
erever symbolic-t
