ETC/프로젝트

[DeFi] 디파이 해킹? 먹튀? Rug Pull

지과쌤 2021. 7. 8.
반응형

글을 시작하기에 앞서 러그풀(rug pull) 이란?

 

 

Rug Pull | CoinMarketCap

A rug pull is a type of scam where developers abandon a project and take their investors' money.

coinmarketcap.com

 

직역하면 양탄자를 쭉 땡긴다는것. 개발자들이 프로젝트를 접고 투자자들의 자금을 탈취하는 악의적인 행위를 말한다.


Wallstreetswap.finance 에 대한 간략한 정보

 

 

Address


소개

Wallstreetswap.finance는 swap, stake, farm 을 할 수 있는 일반적인 DeFi 프로토콜이다.

이 프로젝트는 audit을 받았다.(exploit 이후 문서가 지워졌다.)

필수적인 function들을 고루 갖추고 있었고, 특히 Timelock contract가 있어 신뢰도가 높았었다.

 

보안감사(audit) 관련 이슈

audit docs 중

보안감사 내용을 보면, TechRate팀이 Pool.sol 하나의 파일만 체크했다고 나와있다.(????????????)

게다가 현재 Smart contract Github repo가 사라졌고, PDF파일에 따로 contract code가 첨부되어있지 않기 때문에 오딧이 제대로 진행되었는지 정확히 알 수 없는 상태이다.

(오딧은 역시 헥슬라ㄴ.....)


러그풀(rug pull)

2021년 2월 15일 협정 세계시 오전 11시 30분, Wallstreetswap.finance는 사이트를 닫고 잠적했다.

 

보통 이런 방식들은 토큰을 빠르게 덤핑시켜 금전적인 손해를 끼치는 식으로 진행이 된다.( 일반 사용자들의 토큰은 건드리지 않음) 그래서 보통 사람들은, 직접 pool의 유동성을 제거하고 자신의 토큰을 되찾아와야한다.

하지만, 이번엔 좀 다른 방식으로 진행이 되었다.

 

Pool이 BNB/BUSD 쌍이라고 가정했을 때, 아래 사진의 LP token contract를 보면 BNB와 BUSD의 reserve0, reserve1의 값이 0인것을 확인할 수 있다.

LP contract getReserves function

그리고 아래 사진처럼 BNB contract의 balanceOf function을 실행하여 LP contract의 잔고를 조회해도 역시 0이 나오는것을 볼 수 있다.

(참고로, contract에 따라 sync function을 호출하는 시점이 다를 경우 잔고가 바로 업데이트되지 않을 수도 있기 때문에, 직접 contract call을 해서 체크를 해주는것이 좋다.)

BNB contract balanceOf function

 

그리고 이제, Contract Owner의 지갑을 확인해보자. 꽤 많은 트랙잭션을 볼 수 있는데, LP토큰을 그의 지갑으로 보낸 뒤, 팬케이크스왑을 통해 BNB로 바꾼것을 볼 수 있다. 그리고 약 22,370 BNB를 다른 지갑으로 보낸다.

 

이후, 약 22,370 BNB는 약 4등분되어 Binance hub로 보내졌고, 추적불가능한 상태가 되었다.

Contract Owner transaction history
깔끔하게 먹튀..


코드레벨단에서 분석해보기.

 

Transfering token

어떻게 Contract owner가 pool에 예치되어있는 사용자들의 토큰들을 한번에 빼갈 수 있었을까?

 

아래 트랜잭션 로그를 보자.(사진)

Transaction log

Contract owner가 src 주소에서 dst 주소로 wad만큼 전송하는 Transfer function을 호출했다.

이 경우엔, BNB/BUSD contract인 0x3c8 주소에서 Contract owner의 주소인 0xa75로 1834.95..BNB가 전송되었다.

 

 

그 다음, BNB contract의 Transfer function을 보자.

위 function을 호출할 때, revert되지 않고 잘 실행되려면 특정 조건들을 만족시켜야한다.

 

L58을 보면, Contract owner는 토큰에 대한 권한을 승인받아야 정상적으로 transfer function을 실행할 수 있음을 알 수 있다. 따라서 Contract owner가 토큰에 대한 권한들을 승인받았기때문에 자산을 탈취할 수 있었음을 알 수 있다.

 

Liquidity Pool Contract (LP Contract)

일반적인 유동성 공급 과정을 알아보자.

 

  • Execute the add liquidity function with the desired token and let the contract transfer your tokens to the contract balance
  • The contract then mints you that LP token pairs that can be used in staking
  • You lose both actual tokens, then you gain the corresponding LP token back

 

즉, 유동성을 제공하는데 사용한 토큰들의 소유권이 해당 컨트랙트로 넘어간다는것인데... 대부분의 경우 문제가 없었지만 이번 경우엔 달랐다는 점을 우선 기억하자.

 

위의 단서를 토대로, LP contract 내의 Approval에 대해 먼저 확인해볼것들이 있다.

a part of the LP token contract
constructor and initialize function

initialize function을 보면 이상한점을 발견할 수 있다.

_token0과 _token1의 approve 인자에 _factory 주소와 uint(-1)이 들어가는것을 볼 수 있는데, uint(-1)은 unsigned integer의 최대값이다. (????????????????????????????? 오...)

 

따라서 매번 LP contract를 호출할때마다 uint(-1)만큼 다룰 수 있는 권한을 _factory에게 넘기고, 이후 WallStreetSwapFactory contract를 deploy한다.

 

WallStreetSwapFactory contract

WallStreetSwapFactory contract

 

L396의 createPair function을 보자.

L406을 보면, initialize function을 호출하는걸 볼 수 있다.

인자를 보면, _factory 주소에 feeToSetter를 볼 수 있는데........

음????
어...??????? 어디서 많이 본...
엌.....

 

Contract owner의 계정이다...

 

이로써 왜 transferFrom function이 revert되지 않고 정상적으로 실행이 됐는지 알 수 있게 되었다.

우리가 LP pool에 토큰을 넘기는 순간, Contract owner에게 권한이 바로 넘어가버렸기 때문이다!


결론

Contract owner는 LP creation contract에 백도어 코드를 넣어 매번 LP쌍이 생성될 때 마다 권한을 넘겨받았다.

그리고 모든 토큰을 LP pair contract에서 자신의 지갑으로 보내 최종적으로 모든 자금을 탈취했다.

 

 

반응형

댓글

💲 추천 글