Analyzing Ethereum smart contracts for vulnerabilities
Bernhard Mueller, the creator of MythX, shows how to detect vulnerabilities in Ethereum smart contracts.
Below, we’ll be running Mythril on some intentionally vulnerable contracts from the Ethernaut wargame (thanks to the guys from Zeppelin solutions for giving me permission!). If you haven’t tried the wargame yourself, be aware that there are spoilers ahead! I recommend giving it a shot yourself first if you haven’t already.
Token
The objective in level three of Ethernaut is to hack a basic token contract called Token
. Check out the code to see if you can spot the bug.
When analyzing the smart contracts with Mythril you can choose from three input options:
- Solidity code file: This only works if the solc command line compiler is installed.
- Solidity bytecode: If you don’t have solc, you can compile the code with Remix and pass the runtime binary code to Mythril via the
-c
argument. - Contract address: To scan a contract instance on the blockchain, use the
-a ADDRESS
option.
I’ll be using option 1 below — for detailed instructions on the other input options check out the README.
Copy/paste the code into a text file and save it as ethernaut-token.sol
, then run the myth analyze
command. Mythril outputs detected issues on the console:
$ myth analyze token.sol
==== Integer Underflow ====
SWC ID: 101
Severity: High
Contract: Token
Function name: transfer(address,uint256)
PC address: 436
Estimated Gas Usage: 11935 - 52881
The binary subtraction can underflow.
The operands of the subtraction operation are not sufficiently constrained. The subtraction could therefore result in an integer underflow. Prevent the underflow by checking inputs or ensure sure that the underflow is caught by an assertion.
--------------------
In file: token.sol:13balances[msg.sender] - _value--------------------
(etc...)
In this case Mythril detectsd one integer overflow and two integer underflow issues in the function transfer
. Let’s have a look at the code to see what’s going on:
function transfer(address _to, uint _value) public returns (bool) {
require(balances[msg.sender] — _value >= 0);
balances[msg.sender] -= _value;
balances[_to] += _value;
return true;
}
We can see that balances[msg.sender] — _value
will indeed wrap if _value
is larger than balances[msg.sender]
. In that case the sender will end up with an astronomical amount of tokens!
Fallout
This is level two of the Ethernaut challenge. Have a look at the code first — the problem isn’t that hard to spot!
Here’s what Mythril has to say about it:
$ myth analyze fallout.sol
==== Unprotected Ether Withdrawal ====
SWC ID: 105
Severity: High
Contract: Fallout
Function name: collectAllocations()
PC address: 934
Estimated Gas Usage: 1880 - 36491
Anyone can withdraw ETH from the contract account.Arbitrary senders other than the contract creator can withdraw ETH from the contract account without previously having sent an equivalent amount of ETH to it. This is likely to be a vulnerability.
--------------------
In file: fallout.sol:159msg.sender.transfer(address(this).balance)
--------------------Initial State:Account: [CREATOR], balance: 0x1000001328abc1e, nonce:0, storage:{}
Account: [ATTACKER], balance: 0x0, nonce:0, storage:{}
Account: [SOMEGUY], balance: 0x500d18006a4060402, nonce:0, storage:{}Transaction Sequence:Caller: [CREATOR], calldata: , value: 0x0
Caller: [ATTACKER], function: Fal1out(), txdata: 0x6fab5ddf, value: 0x0
Caller: [ATTACKER], function: collectAllocations(), txdata: 0x8aa96f38, value: 0x0
Mythril claims that it is possible withdraw ETH from the contract using the function collectAllocations()
. But isn’t that function protected by the onlyOwner
modifier? Note that two transactions are shown in the “transaction sequence” section. These is the sequence of function calls that trigger the vulnerability. The first transaction calls a function named Fal1out()
, the second transaction calls collectAllocations()
.
With that in in mind, take another careful look at the source code. You might notice that the constructor name is slightly different from the contract name, and thus compiles into a regular public function that anyone can call to set a new owner! This is similar to the Rubixi vulnerability.
Delegation
Level 4 of Ethernaut is a multi-contract scenario. Fortunately, Mythril can process multiple contracts and understands various types of message calls between contracts. When you analyze a contract on the blockchain Mythril can automatically detect and download dependencies during runtime.
To try this out, I deployed the Delegate
and Delegation
contracts on a local Ganache instance. Linking is accomplished by passing the address of the Delegation
instance to the constructor of Delegate
.
On-chain analyses are launched using the -a ADDRESS
argument. The command shown below also includes three additional flags:
--rpc ganache
activates the Ganache RPC preset;-l
activates the dynamic loader. This tells Mythril to also retrieve and scan any additional referenced contracts;-v4
activates informational debugging output. This will give us some insight into what the loader is doing.
$ myth -v4 --rpc ganache analye -la 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0INFO:root:SVM initialized with dynamic loader: <mythril.support.loader.DynLoader object at 0x102329ef0>
INFO:root:Dynld at contract 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0: Concat(0, Extract(167, 8, storage_1))
INFO:root:Dynamic contract address at storage index 1
INFO:root:Dependency address: 0x28241019d1b3b2b3763a9d4c7f37fca8ab02e449
INFO:root:DELEGATECALL to: 0x28241019d1b3b2b3763a9d4c7f37fca8ab02e449
INFO:root:Unsupported symbolic calldata offset
INFO:root:- Entering function 0x28241019d1b3b2b3763a9d4c7f37fca8ab02e449:owner()
(...)INFO:root:Execution complete, saved 374 states
INFO:root:38 nodes, 37 edges
INFO:root:Resolving paths
INFO:root:Analyzing storage operations...==== Unchecked CALL return value ====
Type: Informational
Contract: 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0
Function name: main
PC address: 171The function main contains a call to an address obtained from storage.The return value of this call is not checked. Note that the function will continue to execute with a return value of '0' if the called contract throws.--------------------==== CALLDATA forwarded with delegatecall() ====
Type: Informational
Contract: 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0
Function name: main
PC address: 171This contract forwards its calldata via DELEGATECALL in its fallback function. This means that any function in the called contract can be executed. Note that the callee contract will have access to the storage of the calling contract.DELEGATECALL target: Concat(0, Extract(167, 8, storage_1))--------------------
Two issues have been identified here:
- Unchecked
CALL
return value in themain
(fallback) function. This seems weird, as we can clearly see that thedelegatecall()
in the fallback function is wrapped into anif
-statement. However, if you check the disassembly, you’ll find that the compiler optimizes this out. CALLDATA
forwarded withdelegatecall()
: Mythril also warns about forwardingmsg.data
throughDELEGATECALL
and notes that arbitrary functions in the called contract can be executed.
Mythril seems to have missed the the fact that the _owner
state variable can be overwritten by calling the pwn()
function. Why is that? If you consider the overall logic of both contracts, you’ll note that even though changing the state variable named _owner
might appear critical, it doesn’t have any further implications (i.e., it doesn’t allow you to do anything you couldn’t have done anyway), so Mythril doesn’t consider it a vulnerability.
About Mythril and MythX
Mythril is a free and open-source smart contract security analyzer. It uses symbolic execution to detect a variety of security vulnerabilities.
MythX is a cloud-based smart contract security service that seamlessly integrates into smart contract development environments and build pipelines. It bundles multiple bleeding-edge security analysis processes into an easy-to-use API that allows anyone to create purpose-built smart contract security tools. MythX is compatible with Ethereum, Tron, Vechain, Quorum, Roostock and other EVM-based platforms.