A look into Parity’s Multisig Wallet bug affecting >$100 million in ether and tokens

Many outlets have already examined the bug e.g. here, here, here and video here. But we will touch on some stuffs which had not been widely presented such as how the bug was not as obvious to the coder, how to track the wallets with events, a message left by the first exploiter and time line of 15 exploiters racing against time.

Constructor

The bug was introduced when the coder was updating the contract to gather all the common functions into a library so that each new wallet can be deployed at 70% cheaper.

Code (in Solidity) before update:

// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function multiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i) {
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}

Code after update:

// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function initMultiowned(address[] _owners, uint _required) {
m_numOwners = _owners.length + 1;
m_owners[1] = uint(msg.sender);
m_ownerIndex[uint(msg.sender)] = 1;
for (uint i = 0; i < _owners.length; ++i) {
m_owners[2 + i] = uint(_owners[i]);
m_ownerIndex[uint(_owners[i])] = 2 + i;
}
m_required = _required;
}

Wait, only the function name is different!
Before the update, the function name multiowned is the same as the contract name, this would mean that the function is a constructor and will only be called once, when the contract is created. After the update, the function name has been changed to initMultiowned and the contract name has been changed too but to WalletLibrary . This meant that this is no longer a constructor and anyone could call this function to change the owners anytime, granting them access to any funds stored in the wallet.

Multisig wallets events

Since anyone can write their own multisig wallet in Ethereum, there is no single standard. However, you can create wallets from the user interface of Mist and Parity thus these are the most widely used wallets. To track their usage, we can look at the following 4 events that could be emitted whenever a wallet makes a transaction.

event SingleTransact(address owner, uint value, address to, bytes data, address created);
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
event SingleTransact(address owner, uint value, address to, bytes data);
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);

The events can be tracked by the hash of their signature. For example, for the first event, the signature is

SingleTransact(address,uint256,address,bytes,address)

The Keccak-256 hash of the signature is

0x9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf292

You can see an example of this event being emitted in a transaction at Etherscan. Etherscan also provides an api for events and you can access one here that fetches all emits of this event from block 4,000,000 to 4,060,000.

If you run a non pruned node, you can run the following nodejs code to retrieve the logs for his event.

let filter = web3.eth.filter({
topics: ['0x9738cd1a8777c86b011f7b01d87d484217dc6ab5154a9d41eda5d14af8caf292'],
fromBlock: 4000000,
toBlock: 4060000,
});
filter.get(function(error, result) {
if (error) {
console.log(error);
} else {
console.log(result);
}
});

Usage of multisig wallets

Here are the number of times the 4 events were emitted per day since the birth of Ethereum. The y axis is in logarithmic scale. As observed, the events are emitted quite often, and it is not obvious which day the bug was fully exploited (marked with bubble). Do note that this data is not filtered and it is possible for non wallets to emit this event too.

Next we weight the count of events with the amount of ether transferred in that event.

Now it is obvious that during the day of exploit, there is huge spike in the weighted events.

Next we filter the events and only show those that were emitted by vulnerable wallets. This can be done by inspecting the code of the contract emitting the event and matching for code snippets unique to the vulnerable contract.

As observed, the vulnerable contract was first used in February 2017. The y axis is in logarithmic scale and shows that these contracts were not widely used relatively. In this case, the day of the exploit is obvious even without weighting the events.

Even if the wallets were not frequently utilized, they could still be storing significant amount of value. Here we look at the amount of ether stored in all vulnerable contracts per day. They could be storing tokens too but this is not reflected here.

We could also factor in ether’s USD price.

There were significant value stored but it took more than 100 days before it was exploited.

1337

This is the first transaction to transfer ether out using the exploit and the following event was emitted.

event SingleTransact(address owner, uint value, address to, bytes data);

The following is the RLP encoded arguments which can also be found at Etherscan.

0x000000000000000000000000b3764761e297d6f121e79c32a65829cd1ddb4d320000000000000000000000000000000000000000000005ac7391989a21040000000000000000000000000000b3764761e297d6f121e79c32a65829cd1ddb4d32000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000021337000000000000000000000000000000000000000000000000000000000000

Decoding it, we can see the message left by the first exploiter in the 4th argument bytes data which serves no other purpose since this is a normal transfer. Only calls to other contracts require the 4th argument.

owner: 0xb3764761e297d6f121e79c32a65829cd1ddb4d32
value: 26,793 ether
to: 0xb3764761e297d6f121e79c32a65829cd1ddb4d32
data: 0x1337

15 exploiters

Once the exploit is out, it is a race to drain more than 500 other vulnerable wallets. Next we look at the race with 14 other exploiters.

Ether and tokens are included and the USD$ value is used here to normalize the y axis. Each of the 15 exploiters is represented by a different color. Interestingly the first exploiter did not continue to drain other wallets. His next 2 exploits were 13 hours later and the next exploiter only started 6 hours after that. The first exploiter also did not choose the 3 largest wallets, which would not have been too hard to locate.

Here is a zoomed-in version with logarithmic scale.

Circles represent ether and triangles represent tokens. Tokens are much less valuable than indicated by their price since it is easier to redeploy a token without the lost tokens and trading liquidity is also much lower. Fortunately 2 of the exploiters had come forward and is willing to return the funds. They are indicated by green and purple above.

Contact us at contact@codetract.io
Visit us at https://codetract.io
Follow us on Twitter and Medium for more updates!