Bybit Hack Explained: A Step-by-Step Breakdown for Beginners

Hello world, let's start from scratch. First of all, what is Bybit and why is the cryptocurrency market crashing these days? 📉 🪙
Bybit is a cryptocurrency exchange that primarily offers trading services for cryptocurrencies. It allows users to buy, sell, and trade them in a variety of ways (e.g., spot trading, derivatives trading, staking, and earn products). This exchange is known for providing a seamless user experience with low fees, a wide selection of cryptocurrencies, and advanced trading features aimed at experienced traders.
The cryptocurrency market has recently experienced significant price fluctuations, with a notable decline in major cryptocurrencies like Ethereum and Solana. A key factor contributing to this downturn is the unprecedented cyberattack on the Bybit exchange, resulting in the theft of approximately $1.5 billion in digital assets.

I’ll provide a detailed breakdown of how this attack works, starting with an explanation of Ethereum Smart Contracts and Upgradeable Proxy Patterns.
Ethereum Smart Contracts
A Smart Contract is a piece of code deployed on the Ethereum blockchain that automatically executes transactions and predefined operations when specific conditions are met. In the blockchain of Ethereum, smart contracts are written in Solidity. It is a high-level, statically-typed programming language used to write smart contracts for Ethereum and other EVM-compatible blockchains. It is influenced by JavaScript, Python, and C++ and is designed to create secure and deterministic smart contracts.
As an example of smart contract written in Solidity, please consider the following snippet of code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
contract SimpleStorage {
uint256 private storedNumber; // Private variable to store a number
address public owner; // Owner of the contract
// Event emitted when the number is updated
event NumberUpdated(uint256 indexed newValue, address indexed updatedBy);
// Modifier to restrict access to the owner
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_;
}
// Constructor runs once when the contract is deployed
constructor() {
owner = msg.sender; // Set the deployer as the owner
}
// Function to set a number (only owner can call it)
function setNumber(uint256 _number) external onlyOwner {
storedNumber = _number;
emit NumberUpdated(_number, msg.sender); // Emit event
}
// Function to get the stored number
function getNumber() external view returns (uint256) {
return storedNumber;
}
}
The previous smart contract (i.e., SimpleStorage) does the following:
- Stores a number (storedNumber) that only the contract owner can update.
- Restricts updates using an onlyOwner modifier to ensure only the deployer (owner) can modify the number.
- Emits an event (NumberUpdated) whenever the stored number is changed, logging the update and sender.
- Allows anyone to retrieve the number using getNumber() without modifying the blockchain (gas-free).
- Initializes the contract owner (owner = msg.sender) when deployed.
Each smart contract is deployed at a specific address (e.g., 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516) and can be easily inspected by using online tools, such as EtherScan:

By using the experimental decompilation feature offered by EtherScan, we can try to partially reverse the definition of this smart contract:
# Palkeoramix decompiler.
def storage:
stor0 is uint256 at storage 0
def _fallback() payable: # default function
revert
def transfer(address _to, uint256 _value) payable:
require calldata.size - 4 >=′ 64
require _to == _to
if 0xfa09c3a328792253f8dee7116848723b72a6d2e != caller:
revert with 0, 'Ownable: caller is not the owner'
stor0 = _to
def unknown1163b2b0(uint256 _param1) payable:
require calldata.size - 4 >=′ 32
require _param1 == addr(_param1)
if 0xfa09c3a328792253f8dee7116848723b72a6d2e != caller:
revert with 0, 'Ownable: caller is not the owner'
call addr(_param1) with:
value eth.balance(this.address) wei
gas 2300 * is_zero(value) wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
def unknown582515c7(uint256 _param1, uint256 _param2) payable:
require calldata.size - 4 >=′ 64
require _param1 == addr(_param1)
require _param2 == addr(_param2)
if 0xfa09c3a328792253f8dee7116848723b72a6d2e != caller:
revert with 0, 'Ownable: caller is not the owner'
static call addr(_param1).balanceOf(address tokenOwner) with:
gas gas_remaining wei
args this.address
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
require return_data.size >=′ 32
mem[ceil32(return_data.size) + 196 len 96] = transfer(address to, uint256 tokens), addr(_param2) << 64, 0, ext_call.return_datamem[ceil32(return_data.size) + 196 len 28]
call addr(_param1).mem[ceil32(return_data.size) + 196 len 4] with:
gas gas_remaining wei
args mem[ceil32(return_data.size) + 200 len 64]
if not ext_call.success:
revert with 0, 'Token transfer failed'
Safe{Wallet} (formerly known as Gnosis Safe) is a smart contract wallet deployed on the Ethereum blockchain and other EVM-compatible chains (such as Polygon, Arbitrum, Optimism, and BNB Chain). It operates as a multi-signature (multisig) smart contract that allows users to set up wallets requiring multiple approvals for transactions, enhancing security.
Upgradeable Proxy Patterns
Once deployed, a smart contract’s code is immutable – it cannot be changed or patched in place. To enable upgradability, developers use Proxy Patterns, where a contract’s logic can be updated without changing its address. In a typical proxy setup, there are two contracts:
• Proxy Contract (Proxy): It holds the persistent storage (state) and delegates calls to an implementation. The proxy’s code is minimal – often just a fallback function that forwards calls using the EVM’s delegatecall opcode.
• Implementation Contract (Logic): It contains the actual business logic and functions. The proxy points to this implementation’s address and uses delegatecall so that state changes happen in the proxy’s storage. Because delegatecall runs the implementation’s code in the context of the proxy’s storage, the proxy’s state is modified as if the proxy itself executed the logic.

In this pattern, the implementation’s address is stored in a specific storage slot of the proxy. The aforementioned Safe uses the Proxy Pattern, allowing upgrades by changing the implementation contract address.
Step-by-Step Breakdown of the Attack
In the following we'll analyze the steps reported by the official account of SlowMist (blockchain security firm established in January 2018 by a team with over a decade of network security experience):
Here are some details of the exploit:
— SlowMist (@SlowMist_Team) February 21, 2025
1) A malicious implementation contract was deployed at UTC 2025-02-19 7:15:23: https://t.co/IvWIcyghW0
2) At UTC 2025-02-21 14:13:35, the attacker used three owners to sign a transaction replacing the Safe’s implementation contract with… pic.twitter.com/MbaUOOw2L2
In brief, the attack can be summarized in the following 5 steps:
- Deployment of Malicious Contracts: The attacker deployed two different smart contracts (i.e., at addresses 0x96221423681A6d52E184D440a8eFCEbB105C7242 and 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516) few days before the actual exploitation. The first contract contains the necessary Solidity code to replace the pointer of the proxy contract in Safe. On the other hand, the other one is the malicious implementation contract containing two backdoor functions (i.e., sweepETH and sweepERC20) used to extract all ETH and token balances out of the wallet.
- AWS S3 Bucket Compromised: A malicious JavaScript injection targeted a file stored in Safe{Wallet}'s AWS S3 bucket. The attack manipulated transactions by modifying details during the signing process. The injected script included an activation condition, triggering execution only when transactions came from Bybit’s contract address or a test contract controlled by the attacker. This is how the attacker forced three Safe’s owners (the multisig signers) to execute the contract upgrade (i.e., changing the pointer of the proxy contract from the original implementation to the malicious one).
- Switching the Proxy to the Malicious Implementation: Once the malicious transaction was executed (after the required signatures were collected), the value of the proxy contract pointer was changed to the attacker’s implementation address. This is analogous to swapping out the vault’s lock system while the key holders thought they were just performing a standard operation. At this point, the attacker had surreptitiously gained full control over the wallet’s behavior – without breaking the multisig directly, but by corrupting its code.
- Execution of Backdoor Functions to Drain Funds: The malicious Safe implementation contract contained two critical functions intentionally not present in the real Safe code:
- sweepETH(address receiver): When called, this function would check that the caller is the specific authorized attacker address, then transfer all ETH held by the Safe to the given receiver address.
- sweepERC20(address token, address to): Similarly, this function required the caller to be the attacker, and if so, would transfer all tokens of a specified ERC-20 from the Safe’s balance to the attacker’s address.


Some interesting resources:
- SlowMist Medium Article
- Certik Bybit Technical Analysis
- Bybit Update about Safe{Wallet} Infrastructure
Do you think upgradeable smart contracts introduce more security risks than they solve? Share your opinion below and join the conversation! We value your insights and experiences. Leave a comment and let’s discuss the pros and cons of upgradeable contracts together! 🤝