Scamming people via Ethereum smart contracts
Posted on by Tijme Gommers.
[TL;DR] Using a ‘feature’ in the Solidity programming language it is possible to create smart contracts on the Ethereum blockchain that look completely legit; however, everyone that sends money to the contract can never get it back due to that ‘feature’.
Alright, lets dive right into it. The code snippet below is an Ethereum smart contract written in Solidity. If you do not understand any of those terms, click on the links to get more information.
Before you try to work out how the contract works, let me tell you it’s a scam contract that contains a Solidity ‘feature’ that causes unexpected behaviour. Below the code snippet you can find an extensive explanation of the contract. The actual contract can be found on Etherscan.
Explanation
I’ll first explain how one would expect the contract to work.
- Someone constructs the contract.
- The variable
owner
is set to the sender’s address. - The method
shuffle
is called.- The
shuffle
method setssecretNumber
to a random number from 0 to 10.
- The
- The variable
- A l33t h4x0r finds the smart contract by syncing the chain or by searching Etherscan.
- He finds out that you can win money if you know
secretNumber
. - There’s a pretty high chance he guesses the number right after a few times, but instead he decides to do something else.
- Although the
secretNumber
is a private variable, he knows you can get it by reading the contract’s storage. - So he reads the storage and finds out the
secretNumber
is 6. Bingo! - He calls the
play
method with 6 asnumber
argument (and with 1.2 ether) and expects to get the ether from the contract.
- He finds out that you can win money if you know
Please note the payable
keyword from the play
method. It enables people to send ether to the method, which will then automatically be stored in the contract.
The big question is; did the l33t h4x0r outplay the contract’s creator/owner?
The scam
Well, that ‘someone’ that constructed the contract wasn’t just someone. It was a scammer who used a ‘feature’ from Solidity to mislead everyone who played the game. There are at least three ways why people fall for this.
- One can keep calling the
play
method and bet on it that he eventually wins. - One knows that the
secretNumber
is not really random, since it’s made up of the date and the previous block hash.- The person calculates the
secretNumber
by getting the variables it was made up of. - The person calls the
play
method using that number.
- The person calculates the
- One knows how to read the contract’s storage.
- The person reads the location of the
secretNumber
from the storage. - The person calls the
play
method using the number on that location.
- The person reads the location of the
Unfortunately none of these methods work.
The ‘feature’
The scammer paid very much attention to the Solidity documentation.
The local variables of struct, array or mapping type reference storage by default. This means they are stored in the contract’s storage.
The Solidity documentation also states that;
The type of the local variable
player
isPlayer
(stored in storage), but since storage is not dynamically allocated, it has to be assigned from a state variable before it can be used. So no space in storage will be allocated forplayer
, but instead it functions only as an alias for a pre-existing variable in storage.
What will happen is that the compiler interprets
player
as a storage pointer and will make it point to the storage slot 0 by default. This has the effect thatsecretNumber
(which resides at storage slot 0) is modified byplayers.push(player)
.
Please note that I modified the variable names in the quotes above to match the contract.
So to elaborate on this a little bit more. Player player;
points to storage slot 0 because it’s an uninitialized storage variable. The uint256 private secretNumber;
also points to storage slot 0 because it’s the first variable that is declared in the contract (and it’s also set on construct). This means whenever you change something in player
, you will change the secretNumber
.
In this case player.addr = msg.sender;
is executed right after the Player player;
line. This means storage slot 0 gets set to msg.sender
. This leads to the fact that whenever you get secretNumber
from the storage after that line of code was executed, it will give you the value of msg.sender
.
But wait, there’s more
Why don’t we just call the play
method with msg.sender
as number
argument.
In theory this could work. But keep in mind that the play
function requires the number to be 10 or lower.
This means that when your address (msg.sender
) is 10 or lower you can win the ether in this contract.
In reality the chance is very very very little that your address is 10 or lower, and therefore you can (almost) never win ether from this contract.
Where does the ether go?
There is a nice kill
method that enables the contract’s creator to destruct the contract whenever the last played date is 24 hours ago. The self destruct sends all the money in the contract back to the owner of the contract (the attacker).