배경

2025년 2월 21일 저녁, Bybit 거래소와 관련된 중대한 보안 사고가 감지되었습니다. 그날 밤 02:16 UTC에 Bybit Cold Wallet 에서 시작된 대규모 이체가 감지되었습니다.

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

401,346 ETH가 이체되었으며, 이는 약 15억 달러에 해당합니다. 여러 차례의 확인 끝에, 이는 Bybit에 대한 공격이라는 것이 판명되었습니다.

Safe{Wallet} 소개

여러 차례의 분석과 조사를 거쳐, 사건 전체의 전반적인 과정이 더욱 명확해졌습니다. 공격자는 먼저 피싱 기법을 사용하여 Safe{Wallet}과 핵심 개발자를 공격하고 AWS의 CloudFront 또는 S3의 액세스 키를 훔쳤습니다. 그런 다음 액세스 키를 사용하여 Safe{Wallet}의 프런트 엔드에 악성 코드를 주입합니다. 우연히도, 영향을 받았을 때 Safe{Walllet}이 archive.org의 Wayback Machine에 포함되었습니다. Wayback Machine에 따르면, Safe{Wallet}의 프런트엔드 코드에 2025년 2월 19일 17시 28분 33초에 악성 코드가 주입되었습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

악성 코드는 프런트엔드 js 파일 _app-52c9031bfa03da47.js에 나타납니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

악성코드의 핵심 논리를 다음과 같이 추출해 보겠습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

전체적인 논리는 비교적 간단합니다. sd.getAddress(), 즉 Safe 지갑의 주소가 ["0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4","0x19c6876e978d9f128147439ac4cd9ea2582cd141"]에 있는 경우,

그런 다음 주소를 0x96221423681a6d52e184d440a8efcebb105c7242로 설정합니다 .

데이터는 0xa9059cbb0000000000000000000000000bdd077f651ebe7f7b3ce16fe5f2b025be29695160으로 설정되었습니다 ...

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

그중 악성 js 코드에는 0x1db92e2eebc8e0c075a02bea49a2935bcd2dfcf4가 있습니다.

이 지갑 주소는 Bybit에서 도난당한 다중 서명 콜드 지갑 주소입니다. 즉, 공격자의 악성 코드는 Bybit의 콜드 월렛을 표적으로 삼은 공격입니다. 이것의 장점은 발각될 가능성을 최소화한다는 것입니다. 다른 사용자가 알아차리지 못하도록 하세요. Safe Wallet이 호출하려는 계약 주소인 수신 주소는 0x96221423681a6d52e184d440a8efcebb105c7242입니다. 이 스마트 계약은 오픈 소스가 아닙니다. 디컴파일 후, 계약 기능이 매우 간단한 것을 알 수 있습니다. 이는 전달 함수만 구현합니다. 해당 기능은 계약의 slot0을 수신자 주소로 변경하는 것입니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

se.data.data는 위의 계약을 호출하기 위한 calldata이며, 처음 네 바이트는 0xa9059cbb이고, 이는 transfer(address receiver, uint256value)의 서명입니다. 영어: 이후 매개변수는 수신자: 0xbdd077f651ebe7f7b3ce16fe5f2b025be2969516 및 값: 0입니다.Bybit 지갑에서 사용하는 Safe{Wallet} 버전은 1.1.1이고, 이 버전에서 지갑의 계약 호출을 실행하는 로직은 MultiSig 주소-[서명]->Wallet MasterCopy 프록시-[대리자 호출]->Safe Wallet 로직 계약-[대리자 호출 | [calldata]->TargetContract Address]이므로 실제로 수정되는 것은 Wallet MasterCopy Proxy의 slot0이며, 이는 지갑이 공식적으로 인수되었음을 의미합니다.

악성 코드에는 또 다른 주소인 0x19c6876e978d9f128147439ac4cd9ea2582cd141이 있는데, 이는 공격자가 공격 과정을 테스트하는 데 사용한 테스트 주소입니다. 즉, 악성 코드는 두 개의 지갑을 하이재킹합니다. 지갑 1은 Bybit의 콜드 지갑이고, 지갑 2는 공격자의 테스트 지갑입니다. Bybit Cold Wallet의 코드 버전이 1.1.1이므로 공격자는 Safe Wallet 버전 1.1.1도 생성했습니다. 생성된 거래는 https://etherscan.io/tx/0x8df9884dd022f900ea7ebcbd0d47356137e3dcc8032e36d8706bed86f158f7c8입니다. 안전 앱은 더 이상 이전 버전의 지갑을 만들 수 없는데, 공격자는 어떻게 이를 만들었을까요?

공격자가 Proxy Factory 1.1.1의 createProxyWithNonce를 호출하여 Ethereum 메인넷에 테스트 지갑을 생성한 것을 볼 수 있습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

따라서 우리는 또한 createProxyWithNonce를 사용하여 Sepolia 테스트 체인에서 버전 1.1.1의 안전한 지갑을 생성했습니다. 생성된 거래는 https://sepolia.etherscan.io/tx/0xa7ce1ce4cb5ccb65eb9a35842ebde8f9a161dd561bce80528dbb7362efc33ff0이고, 생성된 지갑 주소는 0xfdfd440e9d920a8a577eb3d586547986b889ad56입니다.

하지만 우리가 생성한 지갑 주소는 Safe 앱으로 가져올 수 없습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

Safe에서 Multisig 지갑을 생성하는 코드는 다음과 같습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

Safe는 create2를 사용하여 프록시를 생성하여 지갑을 생성하므로(create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) 동일한 주소로 지갑을 생성하려면 MasterCopy, initializer, saltNonce만 동일하게 유지하면 됩니다. 따라서 우리는 공격자와 동일한 매개변수를 사용하여 Ethereum 메인넷의 공격자와 동일한 주소 0x19c6876e978d9f128147439ac4cd9ea2582cd141을 Sepolia 테스트 체인에 생성하고 https://sepolia.etherscan.io/tx/0x3ec6ef599d1df75c9f255f4c1a0edaed8ef992a4fd6ea90af50a05ca962c9e69에서 거래를 생성했지만 해당 주소는 여전히 Safe 앱으로 가져올 수 없었습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

공격자가 이더리움 메인넷에 생성한 주소는 Safe 앱으로 성공적으로 가져올 수 있습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

나중에 공식 문서에서 Safe Wallet이 L1에서 추적 기반을 사용하여 생성된 지갑을 인덱싱한다는 것을 발견했습니다. https://github.com/safe-global/safedocs/blob/f487ace5e0221437277bd9d56fb340794dda424c/pages/advanced/cli-guides/recovery-safe-deployment.mdx#L42

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

이론상, Factory를 통해 생성된 모든 지갑은 Safe app을 사용할 수 있습니다. 공격자가 Safe app의 프런트엔드 코드를 변조했기 때문에 Safe app을 사용할 때만 공격이 발생하기 때문입니다. 하지만 Factory 1.1.1을 통해 생성한 Safe 지갑은 인덱싱이 불가능하여 테스트할 수 없었습니다. 또한 공격자가 생성한 지갑이 Safe에 의해 색인되는 방식도 알 수 없습니다.

안전에 관하여

Bybit은 Safe{Wallet}의 이전 버전인 1.1.1 버전을 사용하므로 최신 버전인 1.4.0은 이 공격(slot0을 사용하여 지갑을 인수)에 사용된 공격 방법에 더 이상 효과적이지 않습니다.

제로 아워 기술 || Bybit 공격 사건 심층 분석 1부 - Safe{Wallet} 소개

delegatecall이 call로 변경된 것을 볼 수 있습니다. 즉, 호출된 계약 자체의 슬롯만 수정할 수 있고, proxy의 슬롯은 수정할 수 없습니다. 하지만 앞으로는 이와 유사한 공격이 발생하지 않도록 예방해야 합니다. 공격자는 자산을 이전하기 위해 거래를 직접 구성할 수 있습니다. 따라서 서명하기 전에 Safe에 내장된 Tenderly를 사용하여 거래를 시뮬레이션하고, 내용이 정확한지 확인한 후 서명하는 것이 좋습니다.