ETC/프로젝트

[DeFi] Unlimited Minting Exploit

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

2020년 12월 28일 +UTC 오전 8시 8분 12초, Cover Protocol의 shield mining contract인 Blacksmith.sol이 털렸다.

해커는 mining contract의 버그를 악용해 COVER token을 무한정 발행하였고, 440만달러 이상을 탈취하였다.


원인

L118의 코드때문에 입금할 때 L121의 pool state를 업데이트하는 과정에서 문제를 발생시켰다.

 

결론부터 이야기하자면, L118 코드가 저곳에 위치했기때문에, Blacksmith contract가 miner에게 초기 설계 또는 기획보다 많은 보상을 주게 되었고, 이러한 취약점이 결국 큰 손실로 이어지게 된것이다.

 


Timeline

 

The initial attacker's timeline

  1. Blacksmith.sol contract에 새로운 Balancer pool이 등록되었다.
  2. 공격자가 Blacksmith.sol contract에 1,326,879.99 BPT 토큰을 예치하였다.
    1. https://etherscan.io/tx/0xd721b0ef2886f14b75548b70d2d1fd82bea085ca24f5de29b833a64cfd8f7a50
  3. 공격자가 withdraw() function을 호출하였고, 703.64 COVER 토큰(당시 약 $176,814.32)과 1,326,878.99 BTP 토큰을 출금하였다.
    1. https://etherscan.io/tx/0xadf27f5dd052482d46fdf69a5208a27cc7352522c7c19bbde5aee18f6ea4373b
  4. COVER 토큰이 처음 팔리기 시작한 트랜잭션은 다음과 같다. https://etherscan.io/tx/0x66128a1685605b1798c852e14db0b0232a56e3bebf7f3f35b168642801754beb 그리고 동시에 다른 여러 계정을 통해서도 COVER 토큰이 발행되었고 판매되었다.
  5. 공격자는 공격백터가 존재하는동안에도 계속해서 minting을 진행하였다.
    1. https://etherscan.io/tx/0xf81fb72ee096e0d7afe4b99a55b723110604fb26ec82846043cfc396e1fa79da

결과적으로 440만달러가 해당 주소로 탈취되었다.

 

Grap Finance Deployer Externally Owned Account (EOA) timeline

  1. Grap Finance : Deployer가 15,255.552810089260015362 BPT (DAI/Basis pool) 토큰을 Blacksmith farming contract에 예치하였다.
    1. https://etherscan.io/tx/0x77490baee41a9b35a6e87d49453c7329c7517c10ce6ce26b4c142692a2877e65
  2. Grap Finance : Deployer가 잔고 1 wei를 남기고 Blacksmith farming contract에서 15,255.552810089260015362 BPT (DAI/Basis pool) 토큰을 출금하였다.
    1. https://etherscan.io/tx/0x88ce99fc1cb695db82d83ce5fe587396744841d3a123687f95b18df6a3106818
  3. 다른 사용자가 그의 전체 잔액인 1,007.599009946121991627 BPT를 Blacksmith contract에서 인출한다. 따라서 Grap Finance, shield mining Blacksmith contract의 DAI/Basis pool은 정확히 1wei의 유동성을 갖고있다.
    1. https://etherscan.io/tx/0xa27fb73caddb1cf24aa7a5afe84eed13db2f0a889a6ee0f3d5e6226a76c0fd9c
  4. Grap Finance : Deployer가 Blacksmith farming contract에 15,255.552810089260015361 BPT(DAI/Basis pool)를 다시 예치하였다.
    1. https://etherscan.io/tx/0xbd1fcda7006ddd58b18cb3bfbd01ef2d1a979be596e1c73be1d7d65fd7eb8215
  5. Grap Finance : Deployer가 rewards를 claim했고, 이전에 남겨뒀던 1wei와 Ethereum Virtual Machine(EVM)의 storage/memory 이슈와 겹쳐 40,796,131,214,802,500,000.212114436030863813 COVER가 발행되었다.
    1. https://etherscan.io/tx/0xca135d1c4268d6354a019b66946d4fbe4de6f7ddf0ff56389a5cc2ba695b035f
  6. Grap Finance : Deployer가 1inch.exchange를 통해 최대한 많은 양의 COVER 토큰을 판매하였다.
    1. https://etherscan.io/tx/0xaf94d9b537a13819e873b37160594af2b1cc70b420d0b160a02e341566866a6b
    2. https://etherscan.io/tx/0x01b3517845ed9c6b7b40d57bd71ac1a89fec080c5b8988f764d8226ac5caa959
  7. Grap Finance : Deployer가 남은 COVER 토큰들을 소각하였다.
    1. https://etherscan.io/tx/0xe6c068ca3605228b2435a414f2b372057340f77d3fe9f1d3967eb1ad128cb5d2
  8. Grap Finance : Deployer가 COVER 토큰을 팔고 얻은 4351(1 + 4350) ETH를 "Next time, take care of your own shit." 메시지와 함께 반환하였다.  
    1. https://etherscan.io/tx/0x23cb9bdf14eed955a84da3f3cfcf296356c0f897dec0b99e85151a7f084a3051
    2. https://etherscan.io/tx/0xc2fd5094c1e108f83222a86bd46b35fc0da35616385d681964b22003643f982e

 

토큰이 무한정 발행될 수 있었던 이유 - (기술적인 관점)

Background

이더리움 smart contracts를 구현하는데 사용되는 Solidity 프로그래밍 언어의 기본 원리를 파헤쳐볼 필요가 있다.

 

contracts가 컴파일되면, Ethereum Virtual Machine(EVM)은 memory와 storage를 관리하는 연산을 시작하게된다.

 

EVM에는 데이터를 저장할 수 있는 memory, storage, stack 세 영역이 있다. 각각의 영역을 이해하면 왜 위와 같은 버그가 악용되었는지 이해할 수 있을 것이다.

 

컴퓨터의 RAM(Random Access Memory)와 마찬가지로, Solidity 내의 "memory" 키워드는 특정 변수에 메모리를 할당한다. 이 경우, 그 특정 변수는 구체적인 함수 내에서만 유효하도록 범위가 지정된다.(지역변수)

 

메모리는 그 함수가 실행되면 초기화가 되지만, 그 함수의 return이 실행되기전에 메모리 내의 데이터를 storage에 push하면 살아남게 된다.

 

Solidity 내의 "storage" 키워드는 변수가 매핑이나 데이터 structure에서 데이터 저장소에 대한 포인터 역할을 할 수 있도록 한다. 데이터 저장소는 function call과 transaction 사이 데이터들을 유지시켜준다.

 

더 자세히 살펴보면, storage는 본질적으로 256비트 단어를 256비트 단어에 매핑하여 저장하는 key-value 쌍 저장소이다.

 

EVM은 register machine이 아닌 stack machine이다. 즉 모든 컴퓨팅 연산이 stack이라고 불리는 data area에서 이뤄진다.

 

stack은 최대 1024 항목을 수용할 수 있다. 하지만, 상위 16개 항목만 쉽게(?) 접근할 수 있기때문에 해당 범위 내에서 swap이 일어나게된다.

 

 

The Bug

해커들은 Cover Protocol의 Blacksmith.sol을 이용했는데, 이는 Cover Protocol 내에서 staker들이 CLAIM과 NOCLAIM 을 통해 특정 프로젝트나 풀의 토큰을 보상으로 받을 수 있게 하는 Shield Mining Contract이다.

 

먼저 L30의 public pools 변수를 확인하자. (storage of data)

L118을 보면, contract가 "memory" 키워드를 통해 pool data를 메모리에 캐시하는것을 볼 수 있다.

 

그리고 L121에서 updatePool(address _lpToken) function이 pool storage의 pool 변수를 사용함에 따라 storage의 pool을 업데이트하는것을 볼 수 있다.

 

L121에서 호출되는 function

여기서 문제가 발생한다.L84의 pool.accRewardsPer function 계산을 위해 updatePool function 내에서 deposit(address _lpToken, uint256 _amount) function의 L118과 동일한 pool 변수를 사용한다. 하지만 storage 키워드를 사용하게된다...!

 

따라서 deposit function 내에서 pool 변수를 변경하여도 "memory" 키워드를 사용하는 변수가 지역변수이기때문에 contract의 on-chain storage에서의 pool mapping을 변경하지 않을 것이다.

 

이후, contract는 storage를 사용하는 updatePool(address _lpToken) function 내의 pool.accRewardsPerToken 을 갱신한다.

따라서 updatePool(address _lpToken) function 내의 pool.accRewardsPerToken은 memory 내의 pool과 관련이 없고 기술적으로 새로운 pool이기때문에 엄청나게 증가하게된다.

 

memory와 storage 사이의 이런 취약성과 오용에 따라 deployer는 여전히 pool에 캐시된 메모리를 다루는 deposit function 안에 있게된다. 따라서 deposit(address _lpToken, uint256 _amount) function의 miner.rewardWriteoff이 잘못 계산되고 잘못된 pool.accRewardsPerToken을 사용하게된다.

 

결정적으로 누구나 claimRewards(address _lpToken) function을 호출하여 엄청난 양의 토큰들을 mint할 수 있게 된다.

 

claimRewards() function은 위에서 강조했던 miner.rewardWriteoff를 참조하는 _claimCoverRewards(Pool memory pool, Miner memory miner) function을 호출하게 된다. 해당 변수가 갖고있는 값이 실제 pool.accRewardsPerToken보다 훨씬 작기때문에, contract는 굉장히 많은 양의 토큰을 발행하게된다.

 


결론

Grap Finance가 탈취한 자금은 반환되었지만, 다른 해커가 탈취한 약 4백만달러 이상 어치의 토큰은 모두 매도되었고, COVER 토큰의 가치는 약 99% 하락하였다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

댓글

💲 추천 글