When the Hunter becomes the Hunted

I was introduced to a strange looking “honeypot” contract on Etherscan recently.

by Caleb Lau


I was introduced to a strange looking contract on Etherscan recently, which was reported that the owner had set it up to steal funds from unsuspecting participants (which in this case, are people who are trying to exploit the contract themselves). Systems set up as such are known as “honeypots”, set up with the intention to attract attackers, however is monitored and has the intention of diverting or exploiting the original attackers, which in this case results in the attackers losing funds.

The contract in question looks similar to the one below on address 0xE668750B9E902b408d228Ffe41abdb76c9d04cfC:
https://ropsten.etherscan.io/address/0xe668750b9e902b408d228ffe41abdb76c9d04cfc

If we inspect the code, we can see the contract is a naively written contract which acts as a wallet protected with a password. A person could quite easily deploy this contract with any arbitrary password, and should there be the event where the owner no longer has access to the original private keys, he/she could still enter the correct password, match it with the hashedpassword as when set up, and retrieve all funds.

The assumption the owner seemingly missed is that people will not be able to see the password as it is hashed and the variables are not set to public, basically “security through obscurity”. Now, everything on the Ethereum network is public, and we could of course retrieve the password when it is sent to set up the wallet, as indicated by the transaction below:

https://ropsten.etherscan.io/tx/0xce6488a8eb2344810738b312d5fbb5884e861efa6dc4eff3486ca66ad3804a94

Getting the transaction data parameters.

The password in hex is 7468697369736d7970617373776f726400000000000000000000000000000000 (padded to 32 bytes), which is simply:

“thisismypassword”

Awesome! It does look like we could exploit the contract and attempt to drain its balance. We will need to send 1 ETH with our transaction, but we will be getting it back as indicated by the code below (right, right??):

You’ll get your money back. I promise!

The unfortunate attacker then sends the transaction….

Sending the transaction with the password.

And…. Gets nothing back. And a couple minutes later two internal transaction appears, draining all ETH from the contract, leaving the attacker 1 ETH poorer.

Internal transactions indicating the contract being drained, this time, correctly.

So what really happened here? This contract is intentionally engineered with the below properties:

  1. Liberal use of “if” statements so transactions do look as though they have successfully executed, when in reality does not fulfill the criteria required. On the other hand require() would throw a revert and it would be clear as day the password is wrong. This allows the contract to look a lot more innocent, potentially tricking multiple attackers.
  2. Written naively to trick attackers into thinking it is badly written code by a new developer who does not know what he or she is doing.
  3. And this is the important bit – Etherscan presently do not show cross-contract calls which sends 0 value. So contracts could execute state changes, but as long as no value is sent, it will not show up on the transaction list. Etherchain on the other hand would list these calls.

Contract 0xA2D98d20eB3496ABb3Ab59D8E90AD069FCF30b59 is the culprit, originally set up to execute a call per point 3 above. This is done through this transaction:

https://ropsten.etherscan.io/tx/0x7f6ecbfa4511d078ebd21c02d2eb83657cb38599086fb6eaf75d603519f98662

The real setup() call.

If we load up the Parity trace for this transaction, we can very quickly see this transaction in fact does two calls, the first headed to the contract 0xA2D9…b59, resulting in an internal call which calls our wallet contract on 0xE668…cfC:

An internal transaction call.

Notice the block number being 4516435, which is before transaction 0xce6488…….804a94 done on block 4516438!

4516435 vs 4516438

And as setup checks if hashedpassword has already been initialised, the password would NOT be “thisismypassword” but whatever password which the attacker has already preemptively set previously.

Notice hashedpassword == 0x0

And to confirm this, let’s check the storage:

Oh my.

The hashedpassword is not keccak256(“thisismypassword”), neither is the owner 0xd77e…ea8 per transaction 0xce6488…….804a94!

In this case this is how the exploit contract looks like. Note that executeExploit is passes 1 ETH and hence appearing on the internal tx list, however could be made similar to the above as it is the true owner anyhow, which obscures the attack sequence slightly more.

Execution of exploit.

And if you’re interested, there’s more to read:
https://medium.com/@gerhard.wagner/the-phenomena-of-smart-contract-honeypots-755c1f943f7b
https://medium.com/coinmonks/an-analysis-of-a-couple-ethereum-honeypot-contracts-5c07c95b0a8d

2018-11-28T23:38:09+00:00 November 28th, 2018|Innovation|

Leave A Comment