Background

On the evening of February 21, 2025, we detected a major security incident involving the Bybit exchange. At 02:16 UTC that night, we detected a large transfer initiated by the Bybit Cold Wallet :

https://etherscan.io/tx/0xb61413c495fdad6114a7aa863a00b2e3c28945979a10885b12b30316ea9f072c,

401,346 ETH was transferred out, worth about 1.5 BillionUSD. After multiple confirmations, it was determined that this was an attack on Bybit.

About Safe{Wallet}

After multiple analyses and investigations, the general process of the entire incident has become clear. The attacker first used phishing to attack Safe{Wallet} and core developers, and stole the Access Key of AWS's CloudFront or S3. Subsequently, the Access Key was used to inject malicious code into the front end of Safe{Wallet}. Coincidentally, when it was affected, Safe{Wallet} was included in archive.org's Wayback Machine. According to the Wayback Machine, the front end code of Safe{Wallet} had been injected with malicious code at 17:28:33 on February 19, 2025.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

The malicious code appears in the front-end js file _app-52c9031bfa03da47.js.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

Let's take out the core logic of the malicious code as follows:

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

The whole logic is relatively simple. When sd.getAddress(), that is, the address of the Safe wallet, is in ["0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4","0x19c6876e978d9f128147439ac4cd9ea2582cd141"],

Then set the to address to: 0x96221423681a6d52e184d440a8efcebb105c7242,

The data is set to 0xa9059cbb0000000000000000000000000bdd077f651ebe7f7b3ce16fe5f2b025be29695160 ...

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

Among them, in the malicious js code: 0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4,

This wallet address is the multi-signature cold wallet address stolen from Bybit. In other words, the attacker's malicious code is a targeted attack on Bybit's cold wallet. The advantage of doing so is to minimize the chance of being discovered. Prevent other users from noticing. The to address, which is the contract address that the Safe wallet is about to call, is 0x96221423681a6d52e184d440a8efcebb105c7242. This smart contract is not open source. After decompilation, it is found that the contract function is very simple. It only implements a transfer function. The function is to change the contract's slot0 to the recipient address.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

se.data.data is the calldata of the above contract, of which the first four bytes are 0xa9059cbb, which is the signature of transfer(address recipient, uint256value). The subsequent parameters are recipient: 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516, value is 0. Because the Safe{Wallet} version used by the Bybit wallet is 1.1.1, the logic of executing the contract call of the modified version of the wallet is MultiSig Address-[Signature]->Wallet MasterCopy Proxy-[Delegatecall]->Safe Wallet Logic Contract-[Delegatecall | calldata]->TargetContract Address, so what is actually modified is the slot0 of the Wallet MasterCopy Proxy, which means that the wallet is officially taken over.

In the malicious code, another address: 0x19c6876e978d9f128147439ac4cd9ea2582cd141 is the test address used by the attacker to test the attack process. In other words, the malicious code will hijack two wallets, Wallet 1 is Bybit's Cold Wallet, and Wallet 2 is the attacker's test wallet. Since the code version of Bybit Cold Wallet is 1.1.1, the attacker also created a 1.1.1 version of the Safe wallet, and the transaction created is: https://etherscan.io/tx/0x8df9884dd022f900ea7ebcbd0d47356137e3dcc8032e36d8706bed86f158f7c8. Since the Safe App can no longer create old versions of wallets, how did the attacker create it?

We can see that the attacker called createProxyWithNonce of Proxy Factory 1.1.1 to create a test wallet on Ethereum mainnet.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

Therefore, we also used createProxyWithNonce to create a Safe wallet on the Sepolia test chain, with version 1.1.1. Our creation transaction is https://sepolia.etherscan.io/tx/0xa7ce1ce4cb5ccb65eb9a35842ebde8f9a161dd561bce80528dbb7362efc33ff0, and the created wallet address is: 0xfdfd440e9d920a8a577eb3d586547986b889ad56.

However, the wallet address we created cannot be imported into the Safe app.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

We can see that the code for Safe to create a Multisig wallet is as follows:

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

Since Safe creates a Wallet using create2 to create a proxy:= create2(0x0, add(0x20, deploymentData), mload(deploymentData),salt), we only need to keep MasterCopy, initializer, and saltNonce the same to create a wallet with the same address. So, we used the same parameters as the attacker to create the same address 0x19c6876e978d9f128147439ac4cd9ea2582cd141 on the Sepolia test chain as the attacker on Ethereum mainnet, and created a transaction of https://sepolia.etherscan.io/tx/0x3ec6ef599d1df75c9f255f4c1a0edaed8ef992a4fd6ea90af50a05ca962c9e69, but the address still cannot be imported into Safe's app.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

The address created by the attacker on the Ethereum mainnet can be successfully imported into the Safe app.

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

Later, we found in the official documentation that Safe Wallet uses trace-base on L1 to index the created wallet, https://github.com/safe-global/safedocs/blob/f487ace5e0221437277bd9d56fb340794dda424c/pages/advanced/cli-guides/recovery-safe-deployment.mdx#L42

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

In theory, all wallets created through Factory can use Safe app, because the attacker tampered with the front-end code of Safe app, resulting in the attack only when using Safe app. However, the Safe wallet we created through Factory 1.1.1 has never been indexed, so it cannot be tested. We also don't know why the wallet created by the attacker can be indexed by Safe.

About safety

Since Bybit uses an older version of Safe{Wallet}, version 1.1.1, the latest version 1.4.0 is no longer effective against the attack method used in this attack (using slot0 to take over the wallet).

Zero Hour Technology || In-depth study of the Bybit attack incident Part 1 - About Safe{Wallet}

We can see that delegatecall has been changed to call, which means that only the slot of the called contract itself can be modified, and the slot of the proxy cannot be modified. However, similar attack methods need to be prevented in the future. Attackers may directly construct transactions to transfer assets. Therefore, we recommend that before signing a transaction, you should first use Tenderly built into Safe to simulate the transaction and confirm that it is correct before signing.