bytecode only analysis of evm smart contracts

Bytecode only analysis of EVM smart contracts

Let's assume we've located a smart contract on the blockchain. We have the contract address where we retrieve its runtime bytecode. We also easily find the transaction receipt data of the contract's deployment, further giving us the init and runtime bytecode. What we're lacking is the source code, what compiler was used (though contract prologue instructions were amended starting in Solidity version 0.4.22, so by inference we know this bytecode is at least v0.4.22 or greater).

Imagine a scenario where a malicious actor is interested in testing this bytecode for vulnerabilities. I'd argue that with an unknown like this the best plan of action is to try to find a cursory perimeter, something you could digest to better understand what's going on internally, and work from there. This is personal preference.

Symbolic execution is a process that can help us do this. In this experiment we will use the Manticore symbolic execution tool to explore all possible states of this bytecode, and start closing in on any vulnerabilities, should they exist.

Manticore creates a folder where you will find the generated testcases. Opening test00000000.summary you'll find a summary of that running state. In test00000001.summary we see that value can be sent to the contract, and in test_00000001.tx we can further see the function selector used to send value: "0xc0e317fb". We know this function is payable.

There's a good amount of output here for one to parse through. We won't walk through what all of it means here as I'd expect most readers have a generic understanding of the EVM's internals and how it works.

We know this contract is payable. Let's assume that the bytecode contract on the blockchain has value.

Assumptions:

we know the "0xc0e317fb" function is payable

on-chain contract has value, let's say it has a value of 1000

If you can deposit value to a smart contract, you may be able to withdraw value from a smart contract (though not always). Perhaps this contract is vulnerable to reentrancy. We will test this using Manticore.

The script to test this is a bit more advanced, and requires an exploiting contract: 'ReentranceExploit.sol' to test.

This will take awhile, as we're inputting symbolic data and values into proxy calls to the target contract. Manticore has a lot to iterate through. In the end, it should generate approximately 51 testcases, most ending in selfdestruction of the exploiting contract. Reading through these test cases you'll see generic behavior, calls ending in stops, reverts, and a long list of selfdestructs.

Making my way through these test cases, in test_00000010.summary it becomes evident that this contract is vulnerable to reentrancy. See below.

There is a lot going on here, but it's rather simple. Our script cycled through symbolic data and values and worked through all possible states, eventually it called a function that allowed a user to withdraw their funds. Here our exploiting contract was prepared with a payable fallback function to receive the funds, and make a call back to the target contract to initiate another withdrawal before the balance in the target contract can be updated.