Detecting Re-Entrancy Attack in Smart Contracts

On 30 April 2016, the DAO was launched on Ethereum Block 1428757. It was an thrilling concept: a decentralized autonomous organization governed by a smart contract on the Ethereum blockchain and managed based on the investors’ votes. The DAO token sale was a vibrant success, boosting fourteen percent of the Ether in circulation. At the time, the funds increased were equivalent to $34 million. It was the most successful crowdfunding event in history. However, the actual operation was less successful unless you consider being hacked and drained of all funds a success. In June 2016, a hacker withdrew 3.6 million Ether from the DAO’s smart contracts, the world’s first large smart contract attack. Finally, parts of the Ethereum community settled to hard-fork the Ethereum blockchain to retrieve the lost funds. However, not everyone was satisfied with the decision, and the original chain lived on as Ethereum Classic. The exploited vulnerability was a re-entrancy bug (called “recursive send bug” at the time) in the splitDAO() function. The relevant parts of the source code are shown below:

function splitDAO(uint _proposalID, address _newCurator) noEther onlyTokenholders returns (bool _success) {
/*** Some checks & create new DAO (elaborate computations removed) ***/
// Move ether and assign new Tokens
uint fundsToBeMoved = (balances[msg.sender] * p.splitData[0].splitBalance) / p.splitData[0].totalSupply;

if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender)== false)
/*** More elaborate computations removed ***/

// Burn DAO Tokens
Transfer(msg.sender, 0, balances[msg.sender]);
withdrawRewardFor(msg.sender); // be nice, and get his rewards totalSupply -= balances[msg.sender]; balances[msg.sender] = 0; paidOut[msg.sender] = 0; return true;}

In brief, the splitDAO() function makes a “child DAO” contract and transfers tokens to the new DAO. The number of transferred tokens is computed according to the user’s balance. Close the end of the function; a reward is sent to the caller via withdrawRewardFor(), which calls the payOut() function.

function payOut(address _recipient, uint _amount) returns (bool) {
if (msg.sender != owner || msg.value > 0 || (payOwnerOnly && _recipient != owner))

if ( { PayOut(_recipient, _amount); return true; } else { return false; }

Note that there is a low-level instruction in the code, i.e., call statement in the payOut() function.

The first problem here is that _recipient is a user-provided address that can point to a malicious contract. The target contract’s fallback function code will be executed because there’s no call data sent with the message call.
The attacker can set up a malicious contract with a fallback function called back into splitDAO(), forming a disturbing recursive loop!
Note that the state variables (for example, the caller’s balance) are updated only *after* the reentrant call. Although tokens are withdrawn at every iteration of the loop, the attacker’s balance is never set to zero!

Our Detection Strategy in

We first identify all message calls to user-provided addresses that also forward gas. Note that Solidity’s transfer() and send() functions set call gas to only 2,300, preventing re-entrancy attacks.

Then if an external call to an untrusted address is caught, we analyze the control flow graph for potential state changes after the call returns. If a state change is identified, then we report the bug.

For more information about smart contract security audit, please visit

ByteScan.Net —

Blockchain Security Audit Team

Author: Dr. Mo Ashouri (Founder of ByteScan)


Telegram: @mokoder





Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store



ByteScan is a Cyber Security Consulting Firm that offers security auditing services for a number of blockchains, with a special focus on third-generation dapps.