Analysis of the DAO Exploit

Analysis of the DAO Exploit

I’m sure you’ve all heard the headlines about the $150M the DAO was stolen by a hacker using a recursive Ethereum send exploit.

This post will be the first in a series of what this might look like, providing a timeline of the attacker’s actions through the blockchain, deconstructing and explaining what went wrong on a technical level. The first post will focus on how the attacker specifically stole all the money from the DAO.

A multi-stage attack

The DAO’s exploit was clearly not trivial; specific programming patterns made the DAO’s weaknesses not only known, but fixed by the DAO’s creators in an early planned update to the framework’s code. Ironically, while they were blogging and celebrating their victory, hackers were preparing and exploiting a vulnerability that targeted the same functionality they had just fixed and drained all of the DAO’s funds.

Let's take a look at the attack. The attacker analyzed DAO.sol and noticed that the "splitDAO" function was vulnerable to recursively sending the pattern we mentioned above: this function ends up updating the user's balance and total, so if we can call this function before it calls splitDAO, we can recursively transfer as much money as we want (code comments are marked XXXX, you may need to scroll down to see it):


The basic idea is this: propose a split. Run the split. When the DAO wants to revoke your response, call this function to execute the split before the revocation is completed. This function will run without updating your balance, and the command we marked above as "the attacker wants to run more than once" will run multiple times. What does it do? The source code is in TokenCreation.sol, and it transfers tokens from the parent DAO to the child DAO. Basically the attacker uses this to get more tokens transferred to the child DAO than he should have.

How does the DAO decide how many tokens to transfer? Of course, it uses a balanced array:

Because p.splitData[0] is the same every time the attacker calls this function (it is a property of proposal p, not the general state of the DAO), and because the attacker can call this function from the withdraw response before the balance array is updated, the attacker can get this code and run the attack as many times as he wants, so that the same amount of funds will be transferred each time.

The first step an attacker needs to take in order to successfully exploit this vulnerability is to have the DAO's withdraw function, which is vulnerable to a deterministic recursive send vulnerability when actually running. Let's look at what needs to happen in the code to make this happen (from DAO.sol):

If a hacker can get the first if statement to evaluate to false, the statement that is marked vulnerable will run. When this statement runs, the following code will be called:

Note how the marked command line specifically is the vulnerable code, as mentioned in the exploit description we linked.

This command line will send a message from the DAO's protocol to "_recipient" (the attacker). "_recipient" of course contains the wrong function, which will call splitDAO again with the same arguments as the attacker's first call. Remember that because all this is happening in withdrawFor in splitDAO, the code in splitDAO that updates the balances has not run. So split will send more tokens to the child DAO, and then request that the proceeds be withdrawn again. It will try to send tokens to "_recipient" again, which will call splitDAO again before updating the balance array.

Will run as follows:

  1. Propose a split and wait for the direct voting period to expire. (DAO.sol, createProposal)

  2. Run split. (DAO.sol, splitDAO)

  3. Let the DAO send a share of tokens to the new DAO (splitDAO -> TokenCreation.sol, createTokenProxy)

  4. Make sure the DAO tries to send you the reward before updating your balance after (3). (splitDAO -> withdrawRewardFor -> ManagedAccount.sol, payOut)

  5. When the DAO is at step (4), it runs splitDAO again with the same arguments as (2). (payOut -> _recipient.call.value -> _recipient())

  6. The DAO will send you more sub-tokens and withdraw your benefits before updating your balance. (DAO.sol, splitDAO)

  7. Return (5)!

  8. Let the DAO update your balance. This will not happen because (7) goes back to (5).

(Side note: Ethereum’s gas technology doesn’t help here. Call.value passes the required gas for the transaction by default, unlike the send function. So as long as the attacker pays, the code will run. This is considered a low-level exploit and means it is unstable)

With the above, we can trace back step by step how the DAO was emptied step by step.

Step 1: Propose a split

The first step is simply to come up with a regular split, as we mentioned before.

The attacker in this step proposes #59 in the DAO on the blockchain, named “Lonely, so Lonely”.

Because this command line:
He had to wait a week for the proposal to be approved. But that's okay, it's just a simple split proposal like any other! No one will pay too much attention to it, right?

Step 2: Get the benefits

This is fully explained in slock.it's previous posts on the matter, Still No Profits Distributed in the DAO! (because none were generated).

Like we mentioned in the overview, this crucial command line needs to be run here:

If the hacker can get the first flagged command line to run, then the second flagged command line will run the default function of his choice (this is called returning splitDAO as we described earlier).
Let's deconstruct the first if statement:

The balance functionality is defined in Token.sol, and of course it looks like this:

The rewardAccount.accumulatedInput() command line is evaluated from the code in ManagedAccount.sol:

Fortunately, accumulatedInput is pretty straightforward to use. Just use the default functionality for the income account!

Not only that, because there is no logic in decrementing accumulatedInput anywhere (it follows the account's inputs across all transactions), all the attacker has to do is send some Wei to the earnings account and our initial condition will not only evaluate to be wrong, but its component values ​​will evaluate to the same on every call:

Remember that because balanceOf refers to the balance, it is never updated, and because the code in splitDAO is never actually executed, paidOut and totalSupply are not updated either, and the attacker is able to claim their tiny share of the revenue without any problems. Since they can claim their share of the revenue, they can run the default function and return to splitDAO.

But do they really need to include a return? Let's look at this command line again:


What happens if the balance of the income account is 0? Then we can get:

If it is not paid, this will always evaluate to false and never stop! Why? Because the original command lines are equivalent, and after subtracting paidOut from both, we get:

How much was paid in the first part? So the check actually looks like this:

But if amountToBePaid is 0, the DAO will pay you anyway. This doesn't make a lot of sense to me - why waste gas in this way? I guess this is why many people think the attacker needs a balance in the earnings account to perform the attack, something they don't actually need. The attack works the same whether it's an empty earnings account or a full one.

Let's look at the DAO's revenue address. From the DAO's account file in Slockit pegs, this address is 0xd2e16a20dd7b1ae54fb0312209784478d069c7b0 . Examining the transactions for this account you'll see this pattern: 200 pages of .00000002 ether transactions to 0xf835a0247b0063c04ef22006ebe57c5f11977cc4 and 0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89 , two malicious transactions from the attacker (which we'll get to later). Each of these transactions recursively calls withdrawRewardFor , which we mentioned above. So there is indeed a revenue account balance in this attack, the attacker just didn't apply it.

Step 3: A huge shortage

There have been some completely unconfirmed allegations on social media that there was a $3 million shortfall in ether on Bitfinex prior to the attack, with claims that the shortfall represented a profit of nearly $1 million.

It is clear to anyone who built and analyzed the attack that certain properties of the DAO (particularly the fact that any split must run the same code as the original DAO) require the attacker to wait through the creation period of the child DAO (27 days) before withdrawing the coins from the malicious split. This gives the community time to react to the theft, either by freezing the attacker's accounts with a soft fork or by rolling back the entire protocol with a hard fork.

Any financially motivated attacker attempting to exploit a vulnerability on the testnet would want to ensure profits, whether it is a potential rollback or fork to short the underlying token. The smart contract triggered by the malicious split caused the price to plummet within a few minutes, providing an excellent profit opportunity, but there is no evidence that the attacker took advantage of this opportunity. We can at least conclude that they were stupid in this regard.

Step 3a: Prevent Exit (Resistance is Ineffective)

Another possibility the attacker must consider is that a DAO split occurred before the attacker drained the DAO. In this case, the attacker would not have access to the DAO funds because another user would have been the sole administrator.

Unfortunately the attacker is clever: there is evidence that all of the attacker's split proposals come from his own terms, ensuring that he holds some permission for any DAO split. We will show later in this post that the DAO split we describe here is vulnerable to the same attack due to the nature of the DAO. All the attacker has to do is propose and execute a split away from the new DAO himself by sending some ether to the payout account after the creation period. If he can do this before the administrators of the new DAO update the code to remove the vulnerability, he will be able to successfully withdraw ether from the DAO that does not belong to him.

Note from the timeline here that the attacker started all of this almost immediately after the malicious split began. I view this as more of an unnecessary humiliation of the DAO than a financially viable attack: having already emptied the DAO, this effort to pick up whatever coins might be left on the table might be an attempt to disrupt the inaction of holders. Many have concluded, and I agree, that the motivation for this attack was to completely destroy the DAO rather than to profit from it. While no one knows the truth, I suggest you use your own judgment.

Interestingly, this attack occurred on the blockchain after it was described by Emin Gün Sirer, but the public had not noticed it before.

Step 4: Run split

Now that we have labored over all the boring technical aspects of this attack, let's move on to the fun part, the action: performing the malicious split. The account that made the transaction after the split is: 0xf35e2cc8e6523d683ed44870f5b7cc785051a77d .

The child DAO they sent funds to is 0x304a554a310c7e546dfe434669c62820b7d83490 . The account that created and initiated the proposal

It is 0xb656b2a9c3b2416437a811e07466ca712f5a5b5a (you can see the call to the proposal that was created in the blockchain history).

Deconstructing the parameters of the construct that created the child DAO leads us to the manager 0xda4a4626d3e16e094de3225a751aab7128e96526 . This smart contract is just a regular multi-signature wallet, most of its past transactions are just adding/removing owners and other wallet management tasks. Nothing interesting.

Johannes Pfeffer has an excellent blockchain-based reconstruction of the transaction involving the child DAO in Medium. Since he has done such an excellent job, I will not spend too much time analyzing the blockchain. I strongly recommend that anyone interested start with this article.

In the next article in this series we will look at the malicious protocol code itself (including the actual deployment of the exploit in a recursive attack). For ease of publication we have not yet completed the full analysis.

Step 4a: Extend the split

This step was a follow-up to the initial update and obscured how the attacker was able to turn a 30x amplification attack (because the Ethereum stack is limited to 128 blocks) into a nearly infinite drain on the account.

Astute readers may have noticed above that even after overwhelming the stack and performing more malicious splits than necessary, the hacker still has zero output to their balance after splitDAO ends via code:

So how does the attacker get around this? Since DAO tokens have a transfer function, the attacker doesn’t have to! All he needs to do is call the DAO’s help transfer function at the top of the stack, from his malicious function:

By transferring tokens to a proxy account, the original account will have zero outputs at the end of splitDAO (note that if A transfers all the money to B, A's account will have zero outputs through the transfer before it can zero output through splitDAO). The attacker can then return the funds from the proxy account to the original account and start the whole process over again. Even if the totalSupply of splitDAO is lost, since p.totalSupply[0] is used to calculate the payment, it is a property of the original proposal and can only be realized before the attack occurs. So even though the ether in the DAO decreases in each iteration. The strength of the attack can remain the same.

The presence of two malicious protocol calls to withdrawRewardFor on the blockchain indicates that the attacker's proxy account is also a contract that can be used for attack, and is simply swapped with the attacker's original account. This optimization saves one transaction per attack cycle, but it seems unnecessary.

Is 1.1 vulnerable?

Because withdrawRewardFor is vulnerable, it is natural for some to ask whether DAO 1.1 is still vulnerable to the same attack after the updated function. The answer is: yes.
Check out the updated functionality (especially the marked command lines):

Notice how paidOut is now updated before the actual payment is made. So how does this affect our exploit? When getRewardFor is called a second time, the malicious second call to splitDAO occurs, with this command line:

will get 0. The call cost will be call _recipient.call.value (0)(), which is the default value for this function, making it worth calling:

Because the attacker paid a large amount of gas when sending his malicious split transaction, the recursive attack was allowed to proceed.
Realizing that they needed 6 days to get 1.2 after 1.1, and that designing a guaranteed secure code would take years, is probably why the DAO's puppet master wanted to let go.

An important conclusion

I think the sensitivity of 1.1 is interesting for this attack: even if withdrawRewardFor itself is not vulnerable, and even if splitDAO without withdrawRewardFor is not vulnerable, the combination is deadly. This is probably why so many people missed this vulnerability so many times: testers are used to testing one function at a time, and assume that calling a safe subroutine will be safe and work as expected.

In the case of Ethereum, even security functions involving sending funds can make your original functions vulnerable to reentrancy. It doesn't matter if they are functions from the default Solidity library or security functions you made up in your head. Be careful to check that the Ethereum code ensures that any function values ​​are fulfilled after any state update, otherwise those state values ​​will need to be reentrant.

What happens next?

I will not get into the crossover debate here or what will happen next with Ethereum and the DAO. That topic has been predicted on every social platform and no one wants to talk about it.

The next step in our series of announcements is to recreate this vulnerability on a testnet using the DAO 1.0 code and demonstrate the code and attack mechanism behind the exploit. Please note that I reserve the right to limit the length of a series if someone beats me on these.

More information

The information provided in this article is just to provide a broad overview and timeline of the attack and a starting point for analysis.

If you have blockchain data or analysis, protocol source code or binaries relevant to the topic described here, please share it with me at phillinuxcom. I will be happy to add it to this post with credits and a full reconstruction of the event within the last 24 hours.


<<:  The end of corruption: Blockchain technology builds transparent government and eliminates corruption

>>:  Ethereum has a security vulnerability again - solar storm "solar-storm" may affect the entire network of smart contracts

Recommend

Credit card companies have no choice but to embrace blockchain technology

Rage Comment : As the impact of blockchain techno...

Come and see if men with four white eyes are difficult to get along with in life

What is a man with four white eyes like? Four whi...

What kind of face can make people rich?

People have different attitudes towards money, bu...

How can the Ethereum blockchain help finance HIV treatment?

Crazy comment : By issuing HEAL bonds on the bloc...

What disaster will you encounter?

What disaster will you encounter? Click here to e...

Your fortune in life from your physical appearance

1. Action-oriented people with "dimples"...

What's wrong with a man with white eyes?

What's wrong with a man with white eyes? We o...

Why does traditional Chinese aesthetics attach importance to the chin?

People with wide chins are born with flesh and bo...

Asus releases two graphics cards for cryptocurrency mining

One of the world’s largest technology hardware ma...

What kind of face does a man have? What kind of face does a man have?

What kind of face is good for a man? In numerolog...

What kind of girl is suitable for blind date

Blind date is a traditional culture that has been...

What does a beautiful and kind-hearted woman look like?

For a woman, being beautiful is far from enough. ...