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.
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:
-c
argument.-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 -x
command. Mythril outputs detected issues on the console:
$ myth -x Desktop/ethernaut-token.sol
==== Integer Underflow ====SWC ID: 101Type: WarningContract: TokenFunction name: transfer(address,uint256)PC address: 469Estimated Gas Usage: 758 - 1043The subtraction can result in an integer underflow.
--------------------In file: Desktop/ethernaut-token.sol:13
balances[msg.sender] - _value
--------------------
==== Integer Underflow ====SWC ID: 101Type: WarningContract: TokenFunction name: transfer(address,uint256)PC address: 551Estimated Gas Usage: 1291 - 1766The subtraction can result in an integer underflow.
--------------------In file: Desktop/ethernaut-token.sol:14
balances[msg.sender] -= _value
--------------------
==== Integer Overflow ====SWC ID: 101Type: WarningContract: TokenFunction name: transfer(address,uint256)PC address: 627Estimated Gas Usage: 6814 - 27479This binary add operation can result in integer overflow.--------------------In file: Desktop/ethernaut-token.sol:15
balances[_to] += _value
--------------------
In this case, it has detected 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!
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 -x ethernaut-fallout.sol==== Ether thief ====SWC ID: 105Type: WarningContract: FalloutFunction name: collectAllocations()PC address: 1018Estimated Gas Usage: 1112 - 1723
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: ethernaut-fallout.sol:25
msg.sender.transfer(this.balance)
--------------------
==== Integer Overflow ====SWC ID: 101Type: WarningContract: FalloutFunction name: allocate()PC address: 1366Estimated Gas Usage: 713 - 998This binary add operation can result in integer overflow.
--------------------In file: ethernaut-fallout.sol:16
allocations[msg.sender] += msg.value
--------------------
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?
If you’re unclear about an issue Mythril reports, you can add the --verbose-report
flag to get additional debugging information.
$ myth -x ethernaut-fallout.sol --verbose-report==== Ether thief ====(...)--------------------DEBUGGING INFORMATION:
Transaction Sequence:{'2': {'calldata': '0x6fab5ddf', 'call_value': '0x0', 'caller': '0xaaaaaaaabbbbbbbbbcccccccddddddddeeeeeeee'},'6': {'calldata': '0x8aa96f38', 'call_value': '0x0', 'caller': '0xaaaaaaaabbbbbbbbbcccccccddddddddeeeeeeee'}}
Note that two transactions are shown in the “Debugging Information” section. These are the transaction Mythril thinks will trigger the vulnerability.
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.
The ‘calldata’ field of the first transaction shown by Mythril contains the leftmost 4 bytes of the Keccak hash of Fal1out()
, the function signature of the wrongly named constructor. You can verify this with the hash utility:
$ myth --hash "Fal1out()"0x6fab5ddf
The second transaction represents a call tocollectAllocations()
:
$ myth --hash "collectAllocations()"0x8aa96f38
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;-v1
activates informational debugging output. This will give us some insight into what the loader is doing.$ myth --rpc ganache -xla 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0 -v1
INFO: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 1INFO:root:Dependency address: 0x28241019d1b3b2b3763a9d4c7f37fca8ab02e449INFO:root:DELEGATECALL to: 0x28241019d1b3b2b3763a9d4c7f37fca8ab02e449INFO:root:Unsupported symbolic calldata offset**INFO:root:- Entering function 0x28241019d1b3b2b3763a9d4c7f37fca8ab02e449:owner()**(...)
INFO:root:Execution complete, saved 374 statesINFO:root:38 nodes, 37 edgesINFO:root:Resolving pathsINFO:root:Analyzing storage operations...
==== Unchecked CALL return value ====Type: InformationalContract: 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0Function name: mainPC address: 171
The 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: InformationalContract: 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0Function name: mainPC address: 171
This 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:
CALL
return value in the main
(fallback) function. This seems weird, as we can clearly see that the delegatecall()
in the fallback function is wrapped into an if
-statement. However, if you check the disassembly, you’ll find that the compiler optimizes this out.CALLDATA
forwarded with delegatecall()
: Mythril also warns about forwarding msg.data
through DELEGATECALL
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.
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.