When evaluating the security of a decentralized application, looking at verified Solidity code on Etherscan is standard practice. However, relying purely on high-level source code introduces a dangerous blind spot. Malicious actors are increasingly deploying contracts where the verified source code looks pristine, but the actual deployed bytecode contains hidden, malicious logic inserted via compilation tricks, compiler bugs, or direct bytecode manipulation.
To truly protect capital in Web3, security auditors, researchers, and advanced trading desks must learn to read smart contract code at the assembly level. By understanding how the Ethereum Virtual Machine (EVM) interprets compiled bytecode, you can reverse-engineer contract execution, verify compliance, and detect zero-day backdoor functions before interacting with a protocol.
The Compilation Pipeline: From Solidity to Opcodes
Before diving into malicious code detection, it is necessary to understand how high-level code maps down to the machine instructions that the EVM executes.
┌────────────────────────────────────────────────────────┐
│ THE EVM COMPILATION PIPELINE │
├────────────────────────────────────────────────────────┤
│ 1. High-Level Source Code ──► Read Solidity/Vyper Code │
│ │ │
│ 2. Compiled Bytecode ──► Hex Strings (60806040...)│
│ │ │
│ 3. EVM Opcodes ──► Low-Level Instructions │
│ (PUSH1, MSTORE, JUMP) │
└────────────────────────────────────────────────────────┘
When a developer compiles a smart contract, the compiler translates the human-readable text into a long hexadecimal string known as runtime bytecode. This bytecode is a continuous sequence of single-byte instructions called opcodes. When you read smart contract code at the bytecode level, you are dissecting these opcodes as they interact with the EVM stack, memory, and persistent storage layout.
The Anatomy of the EVM Dispatcher
Every standard Ethereum smart contract begins with a specific piece of structural plumbing called the Function Selector Dispatcher. When you send a transaction to a contract, the EVM needs to figure out which specific function inside the contract you are trying to call.
The EVM does this by reading the first 4 bytes of the transaction’s data parameter—known as the msg.sig or function selector. The dispatcher is a series of conditional jumps that compares these 4 bytes against a list of hardcoded function signatures embedded within the contract.
When analyzing unverified or suspicious bytecode, the dispatcher is the first place to look:
- Opcodes to Spot: Look for sequential pairs of
DUP1,PUSH4 ([4-byte signature]),EQ, andJUMPI. - The Logic: The contract duplicates the incoming signature, pushes a known signature to the stack, checks if they are equal (
EQ), and conditional-jumps (JUMPI) to the actual function logic if it finds a match. - The Trap: If a malicious contract claims to be a simple token, but its bytecode dispatcher contains twenty different
PUSH4jump targets, the contract contains hidden functions not disclosed in its public documentation.
Spotting Malicious Bytecode Patterns
Malicious engineers rely on predictable opcode structures to execute exploits. When you read smart contract code in its raw form, look out for these three red-flag signatures:
1. The Stealth Owner Backdoor (Access Control Bypass)
A common rug-pull technique involves an unverified contract structure that allows an unauthorized wallet to suddenly call administrative functions like withdrawAll() or mint().
- Opcode Indicator: Look for a
CALLERopcode closely followed by aSLOAD, anEQcheck, and aJUMPI. - The Threat: This indicates that the contract is fetching the transaction sender’s address (
CALLER), reading an address stored in a specific database slot (SLOAD), verifying if they match, and jumping to privileged code. If this check is buried inside a function that claims to be a public swap or burn mechanism, it is a backdoor.
2. Arbitrary Code Execution via Delegatecall
The DELEGATECALL opcode is an incredibly powerful tool used for proxy upgrades. It allows Contract A to execute code from Contract B while preserving Contract A’s state, storage, and balance.
- Opcode Indicator: Look for a standalone
DELEGATECALLinstruction where the target address is loaded dynamically from storage or transaction data rather than being a hardcoded constant. - The Threat: If a contract allows a user-supplied variable or a hidden admin-controlled variable to dictate the target of a
DELEGATECALL, an attacker (or the deployer) can point it to a malicious runtime contract that overwrites storage slots or moves funds instantly.
3. Honeypot Fee Manipulations
Honeypots look like standard tokens that users can buy, but when the user attempts to sell, the transaction fails.
- Opcode Indicator: Search the transfer logic for conditional routes containing
SSTOREinstructions that dramatically alter storage variables representing transfer fees based on the buyer’s address. - The Threat: The contract code dynamically flips a state variable to block outbound transfers (
REVERT) or changes the transfer tax to 100% the moment a non-whitelisted address interacts with the token.
Bytecode Analysis Toolkit
| Tool Category | Software Option | Primary Analytical Purpose |
| Decompiler | Panoramix / Heimdall | Translates raw hex bytecode back into readable, pseudo-Solidity logic |
| Disassembler | Evmdis / Ethersplay | Breaks down hexadecimal strings into sequential, readable EVM opcodes |
| Visual Debugger | Remix IDE / Foundry | Step-through execution logs to observe stack changes and storage overwrites |
| Symbolic Execution | Mythril / Manticore | Automatically tests bytecode logic paths to discover structural bugs and access errors |
Conclusion
Relying solely on verified high-level code leaves you exposed to advanced deployment manipulations. Learning to read smart contract code at the bytecode and opcode level transforms security from a passive guessing game into an active, forensic science. By mastering the structure of the EVM dispatcher and identifying signature sequences like malicious DELEGATECALL targets, unexpected SLOAD verifications, and hidden state changes, you can verify the absolute truth of any contract. In Web3, the ultimate source of truth is not the developer’s text—it is the bytecode executing on the virtual machine.
FAQ
1. How do I get the raw bytecode of a contract if the code is unverified?
You can fetch the exact runtime bytecode directly from the blockchain using a standard JSON-RPC provider call via the eth_getCode method, passing the contract’s public address and the block tag.
2. What is the difference between creation bytecode and runtime bytecode?
Creation bytecode contains the initialization code, constructor logic, and parameters required to deploy the contract. Runtime bytecode is the actual code that remains permanently stored on the ledger to handle transactions after deployment.
3. Can an AI decompiler perfectly reconstruct Solidity code from bytecode?
No. Compiling code strips away variable names, comments, and structural formatting. Decompilers can reconstruct the underlying logic flows, storage mappings, and mathematical operations, but they output “pseudo-code” rather than the original source files.
4. Why would a contract have verified code that differs from its bytecode?
Sophisticated attackers can exploit compiler discrepancies or manipulate the deployment transaction payload directly so that the source code verified on public block explorers maps to a completely different execution pipeline than the bytecode actually running on-chain.
5. How does a reentrancy attack look at the opcode level?
At the opcode level, a reentrancy exploit appears as a CALL instruction that transfers execution control out to an external address before the contract executes an SSTORE instruction to update or deduct the balance state inside its own storage layout.
