B001: Ethereum Honeypots
After hearing about the achievements of various white hackers rescuing funds from vulnerable projects in the wild, I decided to have a look at the latest verified smart contracts on Etherscan out of curiosity to see whether I could spot anything off.
Two contracts caught my eye; a quiz contract and an “alpha” bank that held 20 and 15 Ether respectively. I had heard of honeypots in the past, however, I hadn’t really delved into the code of one until today so I decided to check what each contract utilized as a “trap” to educate myself and see if there was any logical flaw in the way they operate.
I began with the quiz contract, a seemingly innocuous contract that is “initialized” via it’s
Start function that supposedly reveals the answer to the riddle at this transaction. The
responseHash variable meant to hold the hash the answer is compared against is, ofcourse, not exposed publicly but can easily be retrieved via a raw storage read using an Ethereum client’s RPC API.
Doing so reveals the hash
0x527a3e852c67b61aac552b4d372026f34b48c9afe366e76b6c6fa9d1ebb871e3 at the time of writing this article which is quite different than the
keccak256 hash of “ninE” the hustler’s transaction hinted at. Given that a state change occurred, it either was via an EOA-initiated or contract-initiated call. As the contract didn’t really have EOA interactions, I decided to look for any internal calls and voilla, we got our culprit.
I looked at the data payload of the transaction and some things could be immediately deduced; namely the arbitrary address, function signature and arguments the contract relays. I assume this is part of some form of “honeypot toolkit” given that similar copies of the contract’s bytecode can be found via Etherscan.
By decompiling the contract, my suspicions were confirmed as it was quite a straightforward relay contract. An interesting thing I saw was that the external call of its relay is performed before validating the caller and it also allows calls to self which could have had some interesting effects, however, I wasn’t really able to identify any.
I should note that even if the actual answer to the underlying hash is identified, one’s success is left at the mercy of miners given that it would come to a gas race war as I assume the hustler is actively monitoring the contract and would simply change the solution if a valid solution was found.
For this reason, if one manages to indeed identify the solution, I strongly urge them to submit their transaction privately via the Taichi Network or a similar avenue to prevent any race condition from arising.
The bank contract was slightly more deceptive as to how it operates. At first glance, it looks like a contract to which users can deposit funds to and then withdraw them beyond a specified time (past the now timestamp). Although there were really no bonuses, this honeypot is targetted towards opportunistic hackers given that it has a pretty obvious re-entrancy issue.
In order for the re-entrancy to be exploited, however, a deposit needs to already be present and that would have been mined in a separate block given that the minimum
unlockTime that can be specified is
My initial assumption was that the attacker would simply inspect the mempool of Ethereum nodes and submit their own transaction siphoning the funds right after, thereby preventing anyone from exploiting the contract. My gut was telling me there was more to it so I decided to look a bit deeper.
The contract has a
LogFile member that points to a
Log contract instance. The code of the
Log contract is included in the verified source code of the contract; that doesn’t mean it matches the actual code of the instance though as the
LogFile variable is simply set during the contract’s
I looked at the contract the
LogFile member points to, decompiled it and we got this pot’s modus operandi.
The contract is actually quite a bit more complex than it’s source code indicated and contains a function with an unknown signature, however, our main point of interest is how the
Collect call would be prevented. At the end of the
AddMessage function, this very-innocent segment is executed.
Put simply, if the transaction’s originator is not equal to the address stored within the contract and the
_data argument meant to be stored begins with “C”, a non-positive value needs to have been specified. This prohibits any withdrawals from going through under any circumstance, thereby locking in the hustler’s profit regardless of transaction and block ordering.
It was quite fun to have a look at how these types of scams operate, and even moreso to find one targetted specifically for security-savvy people who think that they can get some free ETH.
Key take-away here would be to always double check the contracts you are interacting with and never attempt to interact with unknown contracts or ones not really associated with an interface as that simply begs for trouble.