Create legendary champs by breaking PRNG of Cryptosaga, an Ethereum RPG game (CVE-2018–12975)


Cryptosaga is a RPG game on Ethereum blockchain. Users buy their heroes by Ethereum and fight with monsters in dungeons to level up. There are 4 classes in heroes. When a user buys a hero, the class of the hero is determined by random number. The random numbers are generated with a private variable, block.blockhash(block.number), and now. These are all accessible by anyone, so attacker can predict the random numbers and create legendary heroes. For a better understanding, I recommend to read my previous posts[1,2,3]. There is a more detail descriptions (especially in [1]).


Figure 1. a part of payWithEth() function generating heroes
Figure 2. summonHero() function

Figure 1 shows a part of payWithEth() function that generates heroes and Figure 2 shows summonHero() function. As you can see, _heroRankToMin is a class of hero. If _heroRankToMin is ‘4', a legendary hero will be generated. _heroRankToMin is depends on _randomValue which is generated by random() function.

Figure 3. random() function

Figure 3 shows the random() function. It generates random numbers by keccak256() function with block.blockhash(block.number), seed, and now.

block.blockhash(block.number) is a hash of the current block. It is always ‘0’. In Solidity, blockhash() function is only works for 256 most recent blocks, except the current block[4].

Therefore, keccak256(keccak256(block.blockhash(block.number), seed), now)) is same with keccak256(keccak256(0, seed), now).

seed is a private variable that is declared at 11th among the contract storage variables. Therefore, we can access private variables like this:

web3.eth.StorageAt(0x6a5309dc905e85ce88f33bcd4d4d9a03ac68f847, 11)

Now, all we have to do is predict now. now is same with block.timestamp. We should know the exact timestamp to make new seed more than 9950.

Figure 4. a sample code to get proper ‘now’

I implemented the above code (Figure 4) to get proper now. The example results of the code are as follows:

Figure 5. `now` that makes `seed` more than 9950

Figure 5 shows the several now that makes seed more than 9950. It means that if our transaction is executed at the time among one of the above now, we can make a legendary hero. However, we cannot predict when our transaction is mined by the miner. So, we cannot decide the time when our transaction is executed by the miner. Therefore, I used internal transactions again, just like I did in 1000 Guess[1] and MyCryptoChamp[3]. I deployed a smart contract as follows:

Figure 4. The smart contract to exploit Cryptosaga

Figure 4 shows the smart contract to exploit Cryptosaga. We just call attack() function with time that we want. If attack() is executed at the time that we don’t want, it calls revert(). attack() only calls payWithEth() function when the transaction is executed at the time we want.


Through the exploit contract, we can create heroes at the time we want. However, it is very difficult to avoid revert() in the attack() function because the transactions should be executed at exactly the same time as the time I gave it as an argument. So, it requires many trials to succeed. However, luckily I succeeded in three attempts.

Figure 5. I succeeded the exploit on the third trial

Then, I got a legendary hero.

Figure 5. Legendary hero created by the exploit contract

Honestly, after the success, I tried more than 50 attempts to get more legendary heroes but I failed. It is possible attack but not easy.


When you use blockhash(), you should always remind that it only works for 256 most recent blocks, except the current block. It will return ‘0’ when you try to use it with the current block.