Bybit ETH Cold Wallet Safe UI Compromise / Malicious masterCopy Swap Drain (2025-02-21)

  • Name: Bybit ETH cold wallet Safe UI compromise / malicious masterCopy swap drain
  • Date: 2025-02-21
  • Protocol: Bybit
  • Chain: Ethereum
  • Category: blind-sign payload substitution / signer-quorum compromise / malicious implementation swap
  • Loss: approximately $1.46B to $1.5B, including about 401,346 ETH, 90,375 stETH, 15,000 cmETH, and 8,000 mETH
  • Summary: On 2025-02-21, Bybit’s Ethereum cold wallet 0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4 was drained after attackers turned Safe’s signing workflow into a transaction-representation trap. Sygnia, NCC Group, and Safe-linked disclosures later tied the incident to a 2025-02-04 compromise of a Safe developer macOS workstation, followed by abuse of that developer’s AWS access inside Safe infrastructure from 2025-02-05 onward. On 2025-02-19, malicious JavaScript was inserted into web resources served from app.safe.global; the code only activated when the wallet address matched Bybit cold wallet 0x1db92... and when the signer address matched one of a small hard-coded set. The injected code cloned the original cold-to-warm transfer so the Safe interface could keep showing the benign transaction, but silently replaced the payload actually sent for signature with an operation = 1 delegatecall against attacker contract 0x96221423681a6d52e184d440a8efcebb105c7242, then restored the original transaction object after signature collection so the coordination flow still looked ordinary. Onchain, that delegatecall overwrote Safe storage slot 0 so the wallet’s masterCopy pointed at malicious implementation 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516, whose sweepETH() and sweepERC20() functions then drained the wallet once three valid signatures had been collected. The critical failure was not a missing multisig threshold; it was that the threshold authenticated a different transaction than the signers believed they were approving.
  • Trigger / entry point:
    • Investigators later traced the intrusion back to 2025-02-04, when a Safe developer workstation was compromised, likely through social engineering. Sygnia summarized Mandiant findings that the earliest known activity involved a Docker project named MC-Based-Stock-Invest-Simulator-main making traffic to getstockprice[.]com from the developer’s ~/Downloads path.
    • By 2025-02-05, the attacker was using that developer’s AWS access into Safe infrastructure and remained active there through 2025-02-21. Sygnia said the intruders operated from ExpressVPN IP space with a Kali Linux user agent, attempted but failed to register an additional MFA device, and instead relied on a hijacked active STS session token.
    • On 2025-02-19, JavaScript bundles served from Safe’s S3-backed web interface were modified to include targeted payload-substitution logic.
    • NCC and Sygnia both preserved the hard-coded targeting layer: wallet array wa contained Bybit cold wallet 0x1db92... plus another address 0x19c6876e978d9f128147439ac4cd9ea2582cd141, while signer array ba contained 0x828424517f9f04015db02169f4026d57b2b07229 and 0x7c1091cf6f36b0140d5e2faf18c3be29fee42d97.
    • When Bybit executed a routine ETH cold-to-warm transfer on 2025-02-21, the compromised Safe delivery path supplied the attacker-controlled payload to the signers while preserving the original transfer details in the visible web flow.
  • Root cause:
    • Bybit’s signing ceremony trusted the Safe web application and hardware-wallet blind-signing flow to represent the same transaction.
    • Safe proxy wallets keep their implementation pointer in storage slot 0 as masterCopy, so a successful delegatecall against attacker code could redefine the wallet’s logic without changing its address.
    • The compromised Safe JavaScript exploited that property by replacing a normal transfer proposal with a slot-overwrite transaction while leaving the signers’ review surface looking like the intended internal transfer.
    • Three valid signatures were therefore enough to authorize a malicious implementation swap, even though the human signers believed they were approving something operationally routine.
    • The deeper lesson is that multisig security failed at the representation layer: the system could verify that the signatures were valid, but not that they expressed the signers’ actual intent.
  • Exploit path:
    1. The attacker compromised a Safe developer machine, used that access to operate inside Safe AWS infrastructure, and modified JavaScript served by app.safe.global.
    2. The injected code watched for a transaction proposed from Bybit’s cold wallet and, when triggered, used structuredClone(...) to preserve the original Safe transaction data so the interface could keep showing the legitimate transfer.
    3. Before signature collection, the code changed the transaction actually sent for signing so that to = 0x96221423681a6d52e184d440a8efcebb105c7242, operation = 1, value = 0, safeTxGas = 45746, and calldata encoded attacker implementation address 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516 under the ERC-20-looking selector 0xa9059cbb.
    4. After each signature was collected, the malicious script restored the original transaction object (signedTx.data = origData) before the proposal continued through Safe’s coordination flow, preserving the benign-looking transaction context seen by the rest of the signers.
    5. Transaction 0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad7882 executed that malicious delegatecall, causing the Safe proxy to overwrite slot 0 and repoint masterCopy to the attacker implementation.
    6. Once the wallet’s logic had been replaced, the attacker called sweepETH() and sweepERC20() through the cold wallet itself, draining the ETH and liquid-staking balances without any further multisig ceremony.
    7. The malicious JavaScript was removed from Safe’s web resources roughly two minutes after the exploit transaction, leaving browser-cache artifacts, response-header timestamps, and web archives as part of the public forensic trail.
  • What the attacker needed to be true:
    • Bybit signers had to rely on Safe’s web UI and associated simulation / review surfaces more than on raw signed calldata displayed independently on the device.
    • Blind-signing or otherwise non-human-readable hardware-wallet output had to make the delegatecall payload practically indistinguishable from a normal internal transfer during the signing ceremony.
    • The targeted wallet needed no secondary guard that would reject an unexpected operation = 1 transaction or a masterCopy change even after enough signatures had been collected.
    • Three valid signatures had to be sufficient to execute the slot-overwrite transaction.
    • The attacker’s malicious implementation had to be deployed in advance so the exploit day reduced to signature capture plus drain execution.
  • Technical anatomy worth remembering:
    • This was not a direct private-key theft story. The signatures were real; the representation of what they authorized was false.
    • The hard-coded malicious JavaScript is worth preserving because it shows the attack was targeted, not opportunistic: it checked wallet and signer arrays before activating, and otherwise let Safe behave normally.
    • The key onchain move was not an ERC-20 transfer but a Safe execTransaction with operation = 1, which let attacker code run in the storage context of the wallet itself.
    • The helper contract 0x96221423681a6d52e184d440a8efcebb105c7242 mattered because it exposed an ERC-20-shaped transfer(address,uint256) signature but actually wrote the supplied _to address into storage slot 0, effectively upgrading the wallet to hostile logic without going through a normal-looking admin path.
    • The attacker implementation 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516 then exposed the simple cash-out primitives sweepETH() and sweepERC20(). The dramatic theft happened only after the quieter implementation swap had already succeeded.
    • Safe’s targeted JavaScript restored the original transaction object after signature collection so the attacker could keep the review flow and multisig coordination looking ordinary while still harvesting signatures over the substituted payload. NCC later noted Safe gateway code changes adding matching hash/signature checks, which is a useful clue that this representation mismatch sat partly in the proposal-orchestration layer and not only in the signer UI.
    • The hardware-wallet lesson is narrower than blind signing bad: EIP-712 could show top-level fields, but not the human meaning of nested delegatecall calldata, so the most important semantic change — operation = 1 plus hidden storage-slot overwrite — remained too easy to miss.
  • Representative transactions and addresses:
    • Victim cold wallet: 0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4
    • Initial attack executor: 0x0fa09c3a328792253f8dee7116848723b72a6d2e
    • Slot-overwrite / delegatecall helper: 0x96221423681a6d52e184d440a8efcebb105c7242
    • Malicious implementation: 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516
    • Implementation-swap transaction: 0x46deef0f52e3a983b67abf4714448a41dd7ffd6d32d32da69d62081c68ad7882
    • Main ETH drain transaction (401,346 ETH): 0xb61413c495fdad6114a7aa863a00b2e3c28945979a10885b12b30316ea9f072c
    • 90,375 stETH drain transaction: 0xa284a1bc4c7e0379c924c73fcea1067068635507254b03ebbbd3f4e222c1fae0
    • 15,000 cmETH drain transaction: 0x847b8403e8a4816a4de1e63db321705cdb6f998fb01ab58f653b863fda988647
    • 8,000 mETH drain transaction: 0xbcf316f5835362b7f1586215173cc8b294f5499c60c029a3de6318bf25ca7b20
  • Response / mitigation:
    • Bybit disclosed that the cold wallet transaction was masked, paused relevant operations, and remained solvent by covering the loss while recovery and attribution work proceeded.
    • Safe, Mandiant, Sygnia, NCC Group, and other responders reconstructed the infrastructure intrusion and targeted JavaScript modification path. Safe-linked public statements framed the incident as a compromise of a Safe Wallet developer machine that resulted in proposal of a disguised malicious transaction, rather than a direct break of Safe smart contracts.
    • Safe later rebuilt and reconfigured parts of its infrastructure, rotated credentials, and tightened transaction / signature consistency checks in associated services.
    • On 2025-02-26, the FBI publicly attributed the heist to North Korea’s Lazarus / TraderTraitor cluster.

Three signer-authority promotions this incident collapsed

  1. A Safe-looking transaction object existed on the canonical signing path

    • Bybit really was inside an ordinary cold-to-warm Safe signing ceremony, using the real app.safe.global delivery path and a real transaction proposal shell.
    • That is only the weakest fact: a custody-shaped object was present on the canonical route.
  2. That rendered object still counted as exact signer-intent truth for this transaction

    • The harder question was whether the browser-rendered transfer, the proposal-service object, the Safe hash, and the payload sent to the hardware wallets were still one identical machine-meaningful object.
    • The incident broke here first: the compromised Safe bundle preserved a benign visible transaction while the actual signed payload became an operation = 1 delegatecall against attacker helper 0x96221423681a6d52e184d440a8efcebb105c7242.
  3. That accepted object still deserved root-control authority over the cold wallet

    • Even if a transaction has crossed enough signing ceremony steps to look routine, it should not silently retain the authority to rewrite masterCopy, install new wallet logic, and inherit sweeping rights over treasury-scale assets.
    • This is where privileged-action lane separation, delegatecall escalation, and stored-approval invalidation after signer-intent anomalies should have started to matter.

Pattern tags

Why the exploit worked

  • Bybit’s real signing boundary already included Safe’s web-delivery path, browser bundle, proposal coordination, and hardware-wallet interpretation layer. Once the attacker controlled that representation stack, the multisig threshold authenticated the attacker’s calldata instead of the operators’ intended transfer.
  • The transaction ceremony failed to preserve one machine-checkable object across displayed transfer → signed hash → executed Safe payload. The malicious JavaScript could keep the benign transaction in the visible workflow while switching the actual payload to operation = 1 delegatecall calldata.
  • Safe proxy mutability turned that representation-layer win into root control. Overwriting slot 0 to point masterCopy at attacker code was enough to convert one disguised approval into permanent local wallet authority.
  • Bybit’s cold-wallet flow appears not to have had an independent policy lane that would fail closed on delegatecall, unexpected helper contracts, or masterCopy mutation during what operators believed was a routine cold-to-warm transfer.

Detection and audit pivots

  • Treat any treasury or custody signing ceremony where the UI says transfer but the executed Safe payload uses operation = 1, an unfamiliar helper contract, or storage-affecting side effects as a representation-layer incident first, not merely an admin-key compromise.
  • Preserve and compare the exact transaction hash, EIP-712 fields, proposal object, browser-rendered summary, signer-device prompts, and executed calldata. The central forensic question is whether all review surfaces were still describing one identical transaction object.
  • Audit wallet and custody policy for privileged-shape drift: could an ordinary payment path still authorize delegatecall, module install, owner change, or implementation-slot mutation if the representation layer is compromised?
  • Treat browser-delivery artifacts as custody evidence. CDN / S3 object history, cache files, response-header timestamps, targeted wallet/signer activation logic, and proposal-service consistency logs can be as important as onchain traces in proving how signer intent was subverted.

Failure-prevention checklist

  • Separate routine cold-to-warm transfer authority from any lane that can execute delegatecall, mutate masterCopy, install modules, or otherwise rewrite future wallet behavior. A payment ceremony should not also be an upgrade ceremony.
  • Make proposal bytes, Safe hash, gateway-stored transaction object, device prompt, and executed calldata independently comparable and fail closed if any view diverges. NCC’s post-incident note that Safe added matching hash/signature checks is the right shape of fix, not a minor hardening extra.
  • Treat operation = 1, unfamiliar helper contracts, storage-slot writes, and implementation-pointer movement as privileged action classes that require stronger quorum, slower execution, or out-of-band confirmation even when the surrounding UI labels the action as a transfer.
  • Add signer-cohort runtime attestation for custody-critical browser artifacts: expected bundle hash, expected delivery path, signer-targeted content-drift detection, and rollback proof that every signer cohort is now executing the same clean bytes.
  • Ensure the signer device or an independent approval surface can render the meaningful semantic boundary — at minimum call versus delegatecall, target contract, and privileged action class — rather than only opaque nested calldata.
  • Keep a fast containment path for signer-intent incidents: freeze proposal services, invalidate pending privileged transactions and partially collected signature bundles, purge caches, rotate signer workstations / sessions, and require fresh review after any unexplained signing anomaly.

Audit prompts

  • Can the same Safe workflow that handles ordinary treasury transfers also authorize delegatecall, module installation, owner change, or implementation-slot mutation, and if so what exact machine-enforced split stops those envelopes from hiding inside payout-looking ceremonies?
  • What exact object does each layer verify: browser-rendered transaction, EIP-712 payload, Safe hash, Client Gateway proposal, Transaction Service record, or executed calldata? Where could one layer still accept a different object from the others?
  • If a signer-specific or wallet-specific JavaScript bundle were served from the canonical Safe domain, what control would prove targeted drift quickly enough to stop the ceremony before threshold collection finishes?
  • Do signer devices or secondary review surfaces make the decisive semantic difference legible — especially operation = 1 plus arbitrary helper-contract calldata — or are operators still relying on a browser bundle to explain privileged actions?
  • After a suspected signer-intent compromise, can responders enumerate and invalidate every still-live proposal, partially signed transaction, or cached privileged bundle before the attacker chooses the execution window?
  • If the proposal service or gateway accepted one mismatched review object already, what exact workflow proves that no stale Safe proposal, cached transaction object, or harvested partial signature can still be resumed later under cleaner-looking UI conditions?

Lessons that should already be standard

  • 3-of-n signatures collected is not the same security claim as three humans reviewed the same transaction object.
  • The canonical domain name is not the security boundary when different signer cohorts can still be served different bytes from that domain.
  • delegatecall into arbitrary helper code is already root authority for a proxy wallet whose implementation pointer lives in storage; it should never inherit the ceremony assumptions of a routine transfer lane.
  • Recovery from a signer-facing supply-chain compromise is not complete when a clean source commit exists or a clean bundle is republished; it is complete when operators can prove every signer cohort now executes the intended bytes and every suspect approval object is dead.
  • Once signer-intent integrity is in doubt, queued proposals and partially collected root-action signatures are already dormant authority, not operational residue. They should age out or be invalidated as aggressively as compromised sessions and browser artifacts.

Why this incident matters

  • It is the cleanest preserved example of signer-intent spoofing at scale: the trusted multisig workflow, not the threshold itself, became the exploit surface.
  • It also shows that wallet implementation swaps are not only a protocol-upgrade risk. A custody wallet can be reprogrammed the same way if its proxy pointer is mutable and enough signatures can be harvested over a disguised payload.
  • It is also a sharp bridge across five positive-design questions that are easy to blur together: signer intent truth as a single security object explains how human review, device confirmation, and collected signatures should have stayed bound to one transaction object; stored-privileged-approval persistence explains the darker exploit-side follow-on, where any harvested Safe proposal or partial root-action signature that survived the compromised review moment would already count as dormant authority; stored-privileged-approval expiry and revocation discipline explains why suspect proposals and partially signed root-action objects needed fast invalidation once the signer-facing environment became untrustworthy; execution-envelope identity pinning explains why a Safe root action that can rewrite masterCopy should never be treated like an ordinary transfer envelope; privileged-action lane separation and ceremony escalation explains why a cold-to-warm payment lane should not also authorize future-authority mutation; and exact production artifact attestation and rollback discipline explains why signer-facing browser-bundle integrity and rollback proof were part of the same custody object.
  • Finally, it demonstrates how Web2 compromise above a Web3 signing ceremony can nullify the reassurance people usually take from multi-sig + hardware wallets + simulations.

Internal linkages

Control surface