North Korea Stole $1.4B from Bybit with Just 10 Lines of JavaScript – Here’s How

🚨 TL;DR: Bybit didn’t get hacked in the traditional sense—there was no private key compromise, no smart contract bug, no brute-force attack. Instead, North Korea’s Lazarus Group silently hijacked Bybit’s Safe{Wallet} multisig by injecting a JavaScript patch that modified transactions before signers approved them.

It only took 10 lines of JavaScript to bypass Bybit’s security, gain ownership of the multisig, and drain $1.4B in crypto.

Here’s a full breakdown of how this heist happened, why it worked, and how to defend against this kind of Web3 attack.


🕵️ The JavaScript Backdoor: What Actually Happened

Many people assume that hacking the UI means changing what a user sees. That’s not what happened here.

The attackers didn’t modify what the UI displayed—they intercepted and modified transactions before they were signed.

✅ If a signer from Bybit’s Safe{Wallet} multisig was detected, the malware swapped the transaction’s recipient and calldata before sending it to the signer.
✅ Once the signer approved the transaction, the malware swapped back the original transaction data so that everything looked normal in the interface.

🛑 End result: The signer thought they were approving a legitimate Safe{Wallet} transfer, but they were actually approving a contract takeover.

safe_wallet_js1


🔬 The Web3 Exploit: DelegateCall + Proxy Storage Manipulation

Once the malware patched the transaction, it was sent to this malicious contract instead:

🔹 Target contract (to field):
'''0x96221423681a6d52e184d440a8efcebb105c7242''' https://etherscan.io/address/0x96221423681A6d52E184D440a8eFCEbB105C7242#code

🔹 The Malicious Contract Bytecode (Decompiled to Solidity)

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
    stor0 = _to  # Overwrites storage slot 0

Instead of transferring tokens, the contract overwrites '''stor0''' (storage slot 0) with ''' _to'''.


🛑 Why Does This Matter?

In many proxy contracts, storage slot 0 holds the owner/admin address.

By changing stor0, the attackers became the new owner of Bybit’s Safe{Wallet} multisig.
Since the contract used delegateCall, this change affected the actual Safe{Wallet} storage—not the attacker’s contract.

🛑 Boom. The attackers now had full control of the wallet.


🚀 Why delegateCall Was the Kill Shot

The attack worked because the transaction was executed with operation = 1, meaning it used delegateCall instead of call.

🔹 What’s the difference?
call executes code in the target contract – The malicious contract would have modified its own storage (harmless).
delegateCall executes code in the calling contract (Safe{Wallet}) – The malicious contract modified Safe{Wallet}’s storage instead (catastrophic).

call_vs_delegatecall

🛑 Because of delegateCall, Safe{Wallet} itself was modified, and the attacker became the contract owner.


⚠️ Red Flags That Should Have Been Caught

This attack wasn’t some crazy zero-day exploit—it was entirely preventable with basic security hygiene.

🚨 Here’s what should have been flagged immediately:
1️⃣ A Safe{Wallet} multisig transaction calling an unknown contract via delegateCall
2️⃣ A transfer() function with a value of 0 ❌ (Why transfer 0 tokens?)
3️⃣ Signers blindly approving transactions without verifying calldata
4️⃣ Multisig signers also having upgrade permissions ❌ (Bad opsec)


🛡️ How to Prevent This Type of Attack

No blind signing on hardware wallets – Ledger devices display transaction calldata in raw hex format for a reason.
Require a separate governance multisig for contract upgrades – Signing transactions and upgrading contracts should not be handled by the same keyholders.
Implement transaction allowlists – Don’t let your multisig execute calls to random unverified contracts.
Require multi-layer approvals – Have a verification step before transactions are executed (e.g., check if calldata matches an expected template).


🔴 Worst Part? It Was Preventable.

Bybit didn’t lose $1.4B because of some unstoppable zero-day exploit. They lost it because nobody checked the raw transaction before signing.

✅ The attacker never touched private keys.
✅ They didn’t need an inside job.
Bybit’s own signers unknowingly handed them full control.

This is a wake-up call for every Web3 security team, DAO, and institutional trader using multisigs.

You can’t trust your UI. You have to verify the transaction at the raw calldata level.


🛠 Next Steps: Turning This Into a CTF

I’ll be putting together a CTF version of this attack, along with reference code, so people can see how it works in practice.

💀 Want to try hacking a fake multisig wallet? Repo coming soon. 🚀


💡 Next Steps for You

🔹 Share this post – Don’t let others fall for the same trick.
🔹 Follow for more Web3 security breakdowns – More case studies & CTFs coming soon.
🔹 If you run a multisig, go check your security model. Right now.