Following the DAO attacker’s trail

Following the DAO attacker’s trail

This post is a follow-up to my previous blog post that explains the principles and timeline of the DAO exploit: http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/ (http://www.8btc.com/thedao-exploit-analysis)

Secondary invasion

In the above, I mainly focused on the recursive send of the withdrawRewardFor vulnerability of the DAO, which is also the focus of media and researchers today. In the above, I described an attack that can be amplified by an attacker 30 times and can be repeated in an infinite loop.

I initially thought the implementation details were unimportant, but Joey Krug and Martin Köppelmann did a great job showing how important these details are. As Martin points out, there are indeed two separate exploits that enable a third, more powerful vulnerability.

  1. On splitDAO, a recursive attack from the withdrawRewardFor function would allow you to withdraw 30 times the amount of DAO tokens you were supposed to have access to, but could only be performed once.

  2. It is possible to enter, however, it is a non-recursive attack, on splitDAO, through the withdrawRewardFor transfer, each split you can get double your tokens.

  3. The combination of (1) and (2) allows you to transfer 30 times the amount of tokens you are entitled to, and you can enter as many times as you want.

The attacker implemented the third one, which we have discussed before, but let's look at the second one. We create a contract where the first and third vulnerabilities have been fixed. Let's look at withdrawRewardFor, a perfect contract with no loop entry vulnerability. Let's recall my previous post that the vulnerability of withdrawRewardFor made splitDAO in DAO 1.1 reentrant, and even though it may have been fixed, the problem still exists in the first line of this function:

function withdrawRewardFor(address _account) noEther internal returns (bool _success) {
if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account])
throw;

uint reward =
(balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply – paidOut[_account];

reward = rewardAccount.balance < reward ? rewardAccount.balance : reward;

paidOut[_account] += reward;
if (!rewardAccount.payOut(_account, reward))
throw;

return true;
}

Keep in mind that even if there is no balance in the rewards account and the amount of money paid to the user is 0, the call to payOut still runs and still calls arbitrary code in the recipient's contract:

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

}

The first time getRewardFor runs, it will pay out the legitimate reward. The second time it pays out 0, the third time it pays out 0, the fourth time it pays out 0, and so on. It will also allow the user to return to splitDAO, amplifying what they should have received by 30x.

Note that even the fixed withdrawRewardFor in DAO 1.1 still has the reentrancy vulnerability. This reentrancy does not threaten the balance in the reward account, because the second (third, fourth) time you run this function it pays 0. However, it is still vulnerable to reentrancy attacks.

If we fix this in DAO 1.2, we write the withdrawRewardFor function perfectly so that it is not vulnerable to reentrancy. This function should look like this:

function withdrawRewardFor(address _account) noEther internal returns (bool _success) {
if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply <= paidOut[_account])
return false; // Stop all splitDAO calls from failing, orthogonal change

Only the red part is modified, simply changing < to ≤. Why? The second time you run this function, the value of paidOut[_account] will be equal to the value of the expression on the left. The code has been set before the reentry is triggered:

uint reward =
(balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply – paidOut[_account];
paidOut[_account] += reward;

So this is what withdrawFor should look like for DAO 1.1, it has no reentrancy vulnerability, it is a classic anti-model best practice. This code is simple and easy to read.

In this way, the infinite amplification weakness of splitDAO described in my previous article does not exist, and the attacker will not do this: because the payOut call will not be run when the balance is zero, there is no point in repeatedly running this function.

However, as Joey Krug pointed out, this does not prevent the same exploit from being used to drain a DAO 1.2 using withdrawRewardFunction. The key is that it is possible to call arbitrary code once with withdrawRewardFor in the new case, and once is enough.

The function itself amplifies this vulnerability: when your reward is withdrawn and the DAO's transfer function is called, your balance becomes zero after the token is transferred to your child DAO, and your token will be stored in a new account. The code details can be found in the previous article, mainly involving splitDAO and transfer functions, both of which modify the same balance array non-atomically.

Therefore, any logic that affects a process non-atomically will cause a vulnerability, and if the process is interrupted by a call to execute an arbitrary contract, it will be attacked. So in addition to "writing non-reentrant functions", remember: don't call external contracts, otherwise you can't predict your program flow or state.

There are a few interesting things about this exploit. First, it's slow. Essentially, you're doubling your tokens every time you move them. However, this doesn't affect the success of the attack: it's fast enough, and by fast, nobody can stop you.

Second, in the DAO 1.1 code that we have fixed, the reentrancy vulnerability is eliminated because it requires a balance in the reward account. If the rewardAccount.accumulatedInput() returns a value of 0, the user will never be paid, and our modified function will always return false, never executing potential malicious code. In the published DAO1.1 code, the withdrawRewardFor vulnerability still exists, and the reward account still does not need a balance to execute withdrawRewardFor.

In practice, this doesn't matter, the reward account can be funded, intentionally or accidentally. This means that an attacker can transfer part of its reward each time, and can use the withdrawRewardFor function to break in again, even after the DAO 1.2 has been fixed.

Disadvantages of Solidity

If the first vulnerability I listed didn't exist, then the community would definitely miss the second one. The first one is an example of the anti-model, and we will try our best to prevent it at this point, and the second one is a more subtle example of the anti-model: no re-entrancy of the original function, just re-entrancy of the original contract.

To summarize:

  1. If you use Solidity's calling structure to call external contracts, or if there are any externally called functions in your modified contract, then when your contract runs the external call, you cannot predict the state after that.

  2. In the current situation of the DAO vulnerability being exploited, the above is not a known programming practice. You can take a look at the Solidity call documentation: https://github.com/ethereum/wiki/wiki/Solidity-Features#generic-call-method It ignores many security vulnerabilities and makes developers enter a false sense of security when using this structure.

  3. As suggested above, do not use Solidity's calling structure to call external contract code from your contract, if you can avoid doing so, then never do so. If you can't do it, you should understand that you will lose all guarantees about the program flow at that time.

To me, the above means more than just flaws and vulnerabilities in the DAO contract itself. It means that, technically, these are features designed by the Ethereum virtual machine itself, but Solidity created security vulnerabilities in the contract that were not only ignored by the community, but also by the designers of the language.

In my opinion, 50% of the responsibility for this vulnerability should fall on the Solidity language designer, which may improve the corrective measures for such cases. I disagree with the sole responsibility of poorly written contract code, even if the contract code is written completely according to the language documentation, there will still be such a vulnerability.

A strange request for help

While writing this article, I stumbled upon an interesting point about the DAO reward account: Why did the attacker collect the rewards, who deposited the money into the reward account? Why did the withdrawRewardFor function keep running during the DAO attack?

Let’s look at the DAO’s reward address. The DAO’s accounting information comes from Slockit. The address is: 0xd2e16a20dd7b1ae54fb0312209784478d069c7b0 (https://github.com/slockit/DAO/wiki/Understanding-the-DAO-accounting). Take a look at its transaction records: https://etherscan.io/txsInternal?a=0xd2e16a20dd7b1ae54fb0312209784478d069c7b0&&zero=true&p=220 You can see that there are 200 pages of 0.00000002 ether transferred to 0xf835a0247b0063c04ef22006ebe57c5f11977cc4 and 0xc0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89 , which is the attacker’s malicious contract.

But look at where these coins came from, the account transaction records on the last page: a single money transaction record, sending 0.001 Ethereum from the address 0xe76563eb8413ede9b4a1a3c1f3280a95c4b60a33, at the end of the creation of the DAO.

The same address later invested in The DAO and holds DigixDAO tokens.

My theory is: he was probably a newbie who wanted to invest in the DAO crowdsale, tried to buy programmatically, and failed. But how could he get the main DAO addresses wrong? They are on the homepage of daohub.org, and the reward address appears in a remote corner of the DAO wiki. So why send money to this address? Just someone trying to play a joke on the DAO structure to see how they react?

What's even more interesting is that searching for this account, you can find a post on an Ethereum forum where he seeks help with the Javascript API to send Ethereum. This account has never logged in since posting this post, and this is their only post https://forum.ethereum.org/discussion/7109/web3-eth-gettransaction-returned-null .

This may be an interesting glimpse into blockchain history, and we will never know the answer. If you are the holder of this account, I would like to know your thoughts at the time.

The attacker is not alone

Looking at my previous post, the reason why I was able to analyze the attacker's path so quickly was because I was building such an attack myself before.

A week ago I received an email from Emin Gün Sirer:

Sat Jun 11 2016 17:42:37 Written by Emin G Sirer <> Hey guys,
I figured out how to empty the DAO.

I spent a few hours analyzing the DAO for this vulnerability. Over the weekend, I spent a few hours troubleshooting several potential vulnerabilities in V1.1, and Sirer replied to me:

2016-06-12 13:34:09 Emin G Sirer <> wrote Oddly enough, I was one of the first to spot this potential problem…
I still think splitDAO is vulnerable. balances[] is not zeroed until this function is called, which violates the withdrawal pattern. So I think it might split the DAO multiple times by moving rewardTokens. This happens in lines 640 to 666 of DAO.sol. Am I wrong?
Regardless, this is indeed a statically analyzable vulnerability. I will ask Andrew Miller if he has statically analyzed this issue.

So I spent a few hours analyzing splitDAO, and I figured out that there was no chance of triggering the recursive send vulnerability. I simply said that it was unlikely. I cited timing issues, and that it was impossible to trigger the recursive send either from within splitDAO or when creating the TokenProxy. I demonstrated some possible exploit implementations, and then went back to work.

There were other people on Sirer's team looking at this issue, and when we dug deeper, it became interesting that the attackers weren't the only ones who knew how to trigger this vulnerability.

The DAO incident was inevitable. I wrote this series of articles out of curiosity rather than trying to prove it, but it gives me a little hope about the power of public scrutiny, and we are not too surprised by it.

A short absence

Thanks for reading this series of posts from me, I hope they were a little enlightening.
Due to work reasons, I will not follow theDAO during the weekend. You may see some related news on my Twitter https://twitter.com/phildaian

There’s still a lot to do: We still need to reconstruct a complete blockchain picture of the attack, including where the hacker’s tokens ended up and why. We need to search public test networks to see if the hacker has tested the attack there. We need to carefully decompile and reconstruct his original Solidity code for documentation and analysis.

We should learn from our experience and use best practices (a few ideas: slowly scale up these contracts, stop using calling constructs in Solidity), and as a community we should chart a course forward and decide whether to fork.

Finally, I want to share that interest in smart contract technology is stronger than ever right now, and this is a great opportunity to showcase a stronger, more guided, more organized, and more principled community than we were last week.

I'd like to thank this hacker for spending countless hours developing and testing this exploit. Hats off to you for beating us this time. I'm sure you won't be so lucky next time.


<<:  Exclusive interview with Bloq CEO Jeff Garzik: Greed is the cause of The DAO tragedy

>>:  Swiss government expected to relax regulation of blockchain companies and promote financial technology reform

Recommend

What does the garlic nose mean in a man’s physiognomy?

The nose is a very important one among the five f...

Going fishing is just to experience the face of releasing the fish

Many people go fishing for a big meal. If they ca...

The idea of ​​rushing to develop blockchain standards is premature

Rage Comment : Before fully understanding the imp...

Physiognomy analysis of life: your fate can be seen from your sleeping posture

Everyone is unique, everyone has their own living...

What kind of face will make a woman have good luck in marriage?

As the saying goes, "Men are afraid of choos...

Is a crooked nose good or bad? See the fate of people with crooked noses

People with crooked noses have bad intentions. In...

Ethereum co-founder quits cryptocurrency industry over security concerns

According to a July 17 report from Bloomberg, Di ...

Deutsche Bank Research Report: Bitcoin Does Not Eliminate Intermediaries

A new Deutsche Bank research paper suggests that ...

What does right eyelid twitching mean?

Does eyelid twitching mean something is about to ...

A mole on a man's foot indicates a life of hard work.

What does a mole on a man’s foot mean? As we all ...

What kind of face makes it difficult to get rich

On the road to making money, you must be careful ...

What are the characteristics of a poor woman? How to identify

Being poor is a situation that a person will do e...