Death of random number: Details of a new type random number attack on EOS

SlowMist
8 min readOct 1, 2019

by yudan@SlowMist security team & Jerry@EOS Live wallet

This analysis is based on the previous releases of EOS 1.8, and is not been tested in the new 1.8 release.

0x01 What happened?

Accoring to the intelligence from SlowMist Zone, EOS Play, a DApp of EOS, was hacked on Sep 14, 2019, which results in a losses of tens of thousands of EOS. After the analysis of SlowMist security team, we found that the attacker(muma******mm) adopted a new type random number attack by using the defect of EOS’s economic model.

Before we start the full details analysis, we need to understand some technical background as a foundation.

0x02 Technical background

As we know,resource is the keypoint to ensure the safety running of EOS system, every transaction in EOS will consume the corresponding resource, like CPU, NET and RAM. All these resources are need you to first stake your EOS into system and then get exchange of it through a formula. If you don’t stake enough EOS in the system, you can’t exchange enough resources to initiated a transaction. According to this, the attack happen this time is based on the CPU resource model in the EOS resource. As we know before, CPU in EOS system is a very important and most consumed resource, when DApps in EOS is hot, we ofen suffer from the lack of CPU, and this situation gave birth to lots of DApp for CPU staking to alleviate the problem of tight resource usage. So, you must curious in the calculation of CPU resource. Let’s take a look at an article of MEETONE:
https://github.com/meet-one/documentation/blob/master/docs/EOSIO-CPU.md (it's in Chinese, So you might translate it into Enlish manually :D )

We can informed by the article that the key formula of caluating CPU resource is as follow:

max_block_cpu_usage * (account_cpu_usage_average_window_ms / block_interval_ms) * your_staked_cpu_count / total_cpu_count

In this formula, “max_block_cpu_usage * (account_cpu_usage_average_window_ms / block_interval_ms)” is a constant, the actual variable that affect the CPU usage is the following formula “your_staked_cpu_count / total_cpu_count”, it indicates that your CPU amount is a precentage of the whole CPU amount in the EOS system. And the formula above can be simplified like:

your CPU precentage * constant

It means that if the whole CPU staking amount is increased, but you don’t increase your CPU staking amount, your precentage will get smaller,and vice versa . So, if you don’t have enough CPU, you can’t initiated transactions normally. With these knowledge, let’s start the related analysis of the attack.

0x03 Details of the attack

The attacker account in this attack is muma******mm. By analyzing its behavior, we found that this account initiated lots of defer transaction by “start” action, and an other “start” action was included in these defer transaction, then start a infinity loop.

Since these large numbers of transactions are full of blocks and bring difficulties to the analysis, the first thing is to filter these transactions. Let’s set eyes on the transaction that the attacker receive the result of the bet, and by querying the blocks that used for calculating the bet result, we found that there is only one onblock transaction in this block. It means that this block is an empty block. It’s obviously that this condition is caused by the high CPU price and no one can initiated a transaction. In additions, EOS Play, the DApp hacked this time, use the block id as the factor of bet result calulation.

So, why the attacker choose this way to attack the DApp? Let’s find out how to calculate the block id.

Accoring to the picture, we can see that the block id calculation function finally call the digest() function, and the digest() function’s implementation is to hash the block_header struct itself, so we keep looking on the defination of the block_header:

We can see the variables in block_header struct are:
1. confrimed
2. previous
3. transaction_mroot
4. action_mroot
5. schedule_version
6. new_producers
7. header_extensions

which are corresponding to
1. the blocks that should be confirmed at the start of each epoch
2. the previous block id
3. the block transactions merkle hash
4. the block actions merkle hash
5. the sechedule of the producer to produce the block, it would not change in a certain time
6. the new producer, can be null
7. extensions, is null before 1.8 release

Ok, now, we can see it clearly that besides action_mroot and transaction_mroot, all other variables can be get easily. So we continue to take a deep look at the transaction_mroot and action_mroot calculation.

You can see that transaction_mroot and action_mroot perform a merkle operation on the total digests of the actions and transactions in the pending_block, respectively, and finally get a transaction_mroot and action_mroot. We continue to track how each digest is generated.

The above two are how the transaction digest and action digest is generated. It can be seen that the calculation of transaction digest use cpu and net as the factor, and these two factors are depend on the condition of producer node when execute the transaction, it is easy to get but hard to predict, but action_mroot does not use these two factors, which means it is easy to calculate. So, what we can know now is: If we want to predict a block id, we should first know the uncertain cpu and net usage. But is there a way that not introduce these two factors into the calculation?

Of course, we can!

Let foucs on the only onblock transaction in the bet result block, first we take a look at its defination:

The picture above show a defination of onblock transaction, it can be seen that the data of onblock is the head_block_header, which means the current block information, that is, the previous block information relative to current pending block, next we track the calling process of the function(get_onblock_transaction):

We found a point, here the function set the implict attributes of onblock transaction to true. What’s the effect of this setting? We next look at the produce process of the transaction:

Here we can see, because it set implict to true, at the begining of the block construction, the onblock transaction will not included in the pending block transaction sequence, but included in the pending block action sequence. It’s to say that, the action sequence in the bet result block is not null but the transaction sequence is null. So the bet result block’s transaction_mroot must be 0. In this way, the attacker avoid to introduce the uncertain cpu and net usage, and can only calculate the action_mroot. And the action_mroot is just to calculate the onblock action itself and we can get the onblock data, which is the previous block information relative to current pending block. So, now the attacker can get all information of calcualting a block id and it is not a hard thing for block id calculation.

0x04 Summary of the attack

After analysis of attack details, we can know that at the time the attacker start bet, he didn’t know the block id of the bet result block, because the block id is depanded on the previous block. The only thing that the attacker can do is to calculate the block id at the previous block of the bet result block. But what if the result was not what the attacker need? According to this point, we found a funny thing, when the block id result is not the expected number, the attack will try to initiated a hi transaction into the bet result block to change the result. But because the hi transaction will introduce the cpu and net usage, the attacker can not ensure to control the result, but it still give the attacker a second chance to win the game, and increase the win percentage, just like bet in a second time.

So, In summary, the attack methods are:
1. Buy a large amount of CPU throgh CPU, which makes transactions are hard to initiated.
2. Assume the bet result block is an empty block and calculate the block id.
3. when the result is not as expected, try to send a hi transaction to the result block and change the result.

0x05 How to prevent?

EOS Play was hacked, because of using the future block id as the factor of the bet result, what we can learn from this example is that we should never use the block information as the factor of result. All the schemes that use block information for result are immature and exist the probability of being predicted. Especially when REX lanuched, it is more easier the increase the CPU price.

SlowMist security team suggest all DApp developer that when using the random number especially for those which involving import operations, should adopt a safer schemes of random number generation to decrease the risk of being hacked. Besides, the EOS smart contract firewall FireWall.X (https://firewallx.io), which is incubated by the SlowMist security team, can also be used to secure contract security.

--

--

SlowMist

SlowMist is a Blockchain security firm established in 2018, providing services such as security audits, security consultants, red teaming, and more.