PolySwarm Smart Contract Hacking Challenge Writeup

This is a walk through for the smart contract hacking challenge organized by PolySwarm for CODE BLUE conference held in Japan on November 01–02. Although the challenge was supposed to be held on-site for whitelisted addresses only, Ben Schmidt of PolySwarm kindly shared a wallet so that I could participate in the challenge.The target smart contract called CashMoney featured a set of honeypot tricks that were described in Ben’s talk “Smart Contract Honeypots”. A naïve attacker would call do_guess() function with a number that was compared with a private variable current, which could easily be revealed off-chain. However, the condition would never work as expected because of the following code:

Solidity prior to 0.5.0 allows uninitialized storage pointers. Since the contract was compiled with solc 0.4.25, the code above would silently overwrite the first element in contract’s storage with players[msg.sender].playerNo value. As a result, variable current which is the first in the storage would be changed right before the comparison. In order to make the condition pass, one had to call do_guess() with their player number instead of current value. The contract allowed to update one’s number and name via updateSelf(), which was quite handy since do_guess() had a restriction on the number range (only 0–10).

After the check succeeded it seemed that nothing could stop me to receive the prize. However, it was just the beginning of the long journey into EVM bytecode, as do_guess() reverted for unknown reason. After a short debugging session in Remix it was clear that the following line caused it:

CashMoney address on EtherScan revealed the source code of another contract called WinnerLog, however winnerLog variable pointed to some other contract which had no verified source code. An attempt to verify the source code was unsuccessful which proved the assumption that WinnerLog had some different logic inside. A quick recon on WinnerLog revealed a magic string dogecointothemoonlambosoondudes! in contract’s storage. The first idea was of course to use this string as a username, however do_guess() still reverted. Other variations like reversing this string or using capital letters also failed. As quick attempts happened to be ineffective, I moved to reverse engineering the contract’s bytecode. Although decompiled contract was hardly comprehensible, EtherVM provided some hints on the logic inside logWinner() function.

It was quite obvious that the function could be called only by CashMoney contract, and some operations were done on each byte of the magic string. Therefore I had to find such username that would satisfy some obscure conditions inside a closed-source smart contract code. Sounds like a perfect task for symbolic execution.

Manticore is a symbolic execution tool created by Trail of Bits which supports Ethereum Virtual Machine. It proved to be effective at CTFs, so I decided to give it a try.

Firstly, it was necessary to set up addresses and create the WinnerLog smart contract: