Bernhard Mueller

@muellerberndt

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:

  1. Solidity code file: This only works if the solc command line compiler is installed.
  2. 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.
  3. 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 -x command. Mythril outputs detected issues on the console:

$ myth -x Desktop/ethernaut-token.sol
==== Integer Underflow ====
SWC ID: 101
Type: Warning
Contract: Token
Function name: transfer(address,uint256)
PC address: 469
Estimated Gas Usage: 758 - 1043
The subtraction can result in an integer underflow.
--------------------
In file: Desktop/ethernaut-token.sol:13
balances[msg.sender] - _value
--------------------
==== Integer Underflow ====
SWC ID: 101
Type: Warning
Contract: Token
Function name: transfer(address,uint256)
PC address: 551
Estimated Gas Usage: 1291 - 1766
The subtraction can result in an integer underflow.
--------------------
In file: Desktop/ethernaut-token.sol:14
balances[msg.sender] -= _value
--------------------
==== Integer Overflow ====
SWC ID: 101
Type: Warning
Contract: Token
Function name: transfer(address,uint256)
PC address: 627
Estimated Gas Usage: 6814 - 27479
This 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!

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 -x ethernaut-fallout.sol
==== Ether thief ====
SWC ID: 105
Type: Warning
Contract: Fallout
Function name: collectAllocations()
PC address: 1018
Estimated 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: 101
Type: Warning
Contract: Fallout
Function name: allocate()
PC address: 1366
Estimated Gas Usage: 713 - 998
This 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

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;
  • -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 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: 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: Informational
Contract: 0x64e1b27e8dbd44769dc8f43cb78447760b1bc1f0
Function name: main
PC 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:

  • Unchecked 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.

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.

More by Bernhard Mueller

Topics of interest

More Related Stories