오딧 콘테스트에 참여하다 보면 자신이 생각하는 취약점에 대한 근거를 같이 제출해야 할 때가 있다. 이러한 근거를 PoC(Proof of Concept)
라고 말하고 이를 통해 해당 취약점이 유효하다는 것을 증명한다. 지금까지 콘테스트에 참여하면서 PoC
를 작성하는 부분이 많이 부족했다. 다른 리포트를 보면 대체적으로 테스트 코드를 작성한다던가, 해킹 시나리오를 글로 풀어서 작성하기도 한다.
하지만 복잡한 시나리오의 경우, 글로만 작성하는 것에는 전달의 한계가 있다. 해당 시나리오가 어떤 코드를 통해 어떻게 진행되는지 조금 더 명확한 근거가 필요하다. PoC
는 다른 리포트를 참고해서 많이 작성해보면 늘겠지만 아무래도 경험이 부족한 내 입장에서는 기초부터 설명이 잘 되어있는 가이드가 필요했다.
여느 때처럼 트위터를 돌아다니다 SunSec
이라는 오디터 계정을 발견했다. 최근에 OnChain Transaction Debugging
이라는 이름으로 PoC와 관련된 레슨을 깃헙에 올리길래 마침 잘됐다 싶었다. 현재는 Lesseon 4까지 나온 상태이며, 1강은 Debugging 툴에 대한 소개여서 패스했다.
지갑에서 트랜잭션을 날리면 이더스캔을 통해 여러 정보들을 확인 할 수 있다. 위 예시에서 651.13개의 테더가 이동했다는 것을 알 수 있다.
이번에는 같은 트랜잭션을 디버깅 툴인 Phalcon을 통해 살펴보자. 조금 더 간결하게 볼 수 있다. 한 가지 다른 점은 아랫부분의 value
값이다. 651.13개로 표시되는 것이 아니라 651,130,000
으로 표시되어 있다. 이는 테더의 decimal이 6이기 때문에 뒤에 6개의 0을 붙여서 나타낸 것이다.
이번에는 유니스왑을 통해 테더를 UNDEAD 토큰으로 스왑했다. 유저가 MEV Bot을 통해 유니스왑으로 거래했다는 것을 알 수 있다.
마찬가지로 Phalcon으로 살펴보면 MEV Bot이 UNI-V2.swap
함수를 호출해서 스왑을 진행했다는 것을 알 수 있다. 이를 재현하기 위해 DeFiLabs repo를 git clone
으로 가져와서 유니스왑 컨트랙트를 테스트 해보자.
forge test --contracts ./src/test/Uniswapv2.sol -vvvv
해당 테스트 코드는 유니스왑 함수를 호출해서 1BTC를 16,788개의 DAI로 스왑한다. (지금은 비트코인이 올라서 23,587개의 DAI로 교환할 수 있다..)
커브 풀에 유동성을 제공하는 트랜잭션을 보자.
Phalcon을 통해 자세히 살펴보면 총 3단계로 트랜잭션이 구성된다.
Pool.liquidity
를 호출한 다음USDT.transferFrom
으로 USDT를 전송해서3Crv.mint
로 유동성을 공급했다는 증표를 민팅한다.
토큰을 전송하는 것 이외에도 거버넌스 제안을 올리는 트랜잭션이 있다. 아래 Decode InputData
를 눌러보면
어떤 제안을 했는지 확인할 수 있다.
forge test --contracts ./src/test/Uniswapv2_flashswap.sol -vvvv
앞서 가져왔던 repo에서 Flashswap을 재현해보자.
총 2단계로 나누어져 있다.
swap
을 통해 100 이더를 빌려오고uniswapV2
를 통해 다시 100이더를 갚는다.
FlashLoan과 FlashSwap은 비슷하지만 살짝 다르다. 둘 다 증거금이 필요하지 않다는 공통점이 있지만, 빌린 토큰을 그대로 갚아야 하는 FlashLoan과 다르게 FlashSwap은 빌린 페어에서 어떤 토큰으로 갚든지 상관없다.