Defeating EOS Gambling Games: The Tech Behind Random Number Loophole
In the past month, blockchain security company PeckShield has exposed hackers attacking eight EOS gambling games, including EOSBet, EOSCast, FFgame, EOSDice, EOSWin, MyEosVegas, LuckyGo, and EOSLelego. Besides hackers gaining 170,503.5 EOS tokens , these attacks are threatening the overall security of the EOS ecosystem.
After studying the attack signatures of several games, PeckShield security engineers concluded that, 1) the hacker accounts were related, and these were organized group attacks, 2) most successful attacks were related to the random number loophole, and 3) similar attacks have become more frequent, and their success rates are improving.
Most EOS gambling games are not open-sourced, so to study the techniques behind the scene, we will use EOS.WIN game as an example, from the viewpoint of the hackers, to reveal the secrets behind these attacks.
On 11/12, the PeckShield situation monitoring platform detected that, in just one minute between 08:59 to 09:00, a hacker attacked EOS.WIN game contract (eosluckydice) 10 times, and gained 9,180 EOS tokens. The hacker first used small amount test attacks at 22:46 the day before, and after 165 tries, he started the real attack using several related accounts at 9:00 on 11/12. Although this game used deferred transaction for random number generation, the hacker still worked around the limitation and attack the game successfully.
Hacker’s attacking and winning techniques
In EOS.WIN’s dice game, a player picks a number, the system gives a reward rate according to the number picked. Then the system generates a random number and if it’s within the range the player picked, player wins and the winning amount is betting amount multiplied by the reward rate.
The playing procedure is as follows: The game contract receives player’s transaction request, delays 1.5 seconds, then calls the ‘resolved’ function. ‘resolved’ function generates a random number and determines if the player wins, then uses an internal call to notify the player (‘receipt’ function), and increments bet_id by one and saves it for later usage. Figure 1 shows the procedure.
PeckShield security engineers found that, the result from its random number generating function, get_random(), may be affected by transaction hash (txid), block height (tapos_block_num), block prefix (tapos_block_prefix), and bet_id, etc.
Some background information first
- Delayed transaction and tapos_block_prefix: Most random generators use tapos_block_num and tapos_block_prefix, and utilizes information in future blocks to generate randomness. However, if a contract uses delayed transaction, i.e., specifying a delay time, although it uses information in the future, but when this transaction starts, the head_block is known, then tapos_block_num and tapos_block_fix are also determined values.
- Transaction status rollback: In EOS transactions, if one action fails or raises exception, the transaction status will be rolled back. For example, if an account deploys an contract, it raises an exception every time it receives a transfer receipt, then it could cause the transaction to fail, and all information including account balance would stay the same.
- Transaction hash ID: One transaction can include several actions. If all action parameters are known, then adding the above mentioned tapos_block_prefix (ref_block_prefix) information, we can calculate the transaction hash ID.
In summary, the attacker exploited features like transaction status rollback and random number incorporating bet_id, used several contract accounts to send transaction requests at the same time, to make sure the last account getting the expected bet_id and winning the price. In EOS.WIN game, the attacker used 5 accounts to bet in small amounts and improve the winning rates, then the last account bets large amount and wins the price. And here is the detailed procedure:
Detailed attack procedure (as shown in Figure 2)
- The attacker deployed 6 attack contracts, and they sent transaction requests at the same time, so they would be ‘resolved’ in the same block. Therefore, the tapos_block_num and tapos_block_prefix would be the same, only the bed_id could be different.
- The attacker’s first 5 contracts, when receiving betting result notice, could determine if the current bet_id is a winning one or not
- If the current bet_id didn’t win, it processes the notice, then the bet_id is updated and later accounts would have new bet_id and new random number.
- If the current bet_id won, it raises an exception when receiving result notice, so the bet_id would be kept the same, and the last account would win also.
From the previous discussion, we can see that winning rate would be higher if more attack accounts were deployed. In the EOS.WIN Dice game, suppose the betting number is 20, using 6 attack accounts, the last account’s winning rate would be about 74%, much higher that the normal 20% rate.
Suggestions to game developers
PeckShield would like to suggest to DApp developers, avoid using variables which could be controlled by players such as bet_id for random number generation, and avoid putting resolving action and notification action (receipt) in the same transaction.
PeckShield Inc. is a leading blockchain security company with the goal of elevating the security, privacy, and usability of current blockchain ecosystem. For any business or media inquires (including the need for smart contract auditing), please contact us at telegram, twitter, or email.