ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Ethernaut 풀이] 이더리움을 해킹해보자 - 6.Delegation
    BlockChain/Technology 2021. 8. 12. 00:53

    오늘의 풀어볼 문제는 Delegation 입니다.

     

    delegatecall 사용은 특히 위험하며 여러 해킹의 공격 벡터로 사용되었습니다.

    delegates는 당신의 컨트랙트에 대하여 완전한 접근 권한을 가집니다.

    강력한 기능인만큼 위험하며 The Partiy Wallet Hack Explained은

    이 아이디어가 어떻게 3천만 달러를 훔치는데 사용되었는지에 대해 다뤘습니다.

     

     

     

     

    ≪ Ethernaut 풀이 시리즈 

     

     

    [Ethernaut 풀이] 이더리움을 해킹해보자 - 1.Fall back

    [Ethernaut 풀이] 이더리움을 해킹해보자 - 2.Fallout

    [Ethernaut 풀이] 이더리움을 해킹해보자 - 4.Telephone

     


    1. 목표 확인

    이번 level의 목표는 주어진 instance의 owenership을 가져오는 것입니다. 이전 level의 목표들과 같지만 풀이를 위해서 새롭게 나온 개념이 있습니다. 바로 delegatecall 입니다. 컨트랙트에서 다른 컨트랙트의 함수를 호출하는 방법은 솔리디티 내에 여러 방법이 존재합니다. delegatecall도 그중 하나의 방법입니다. 

     

    delegatecall (대리함수 실행)

     

    • call과 delegatecall 연산(op code)는 이더리움 코드를 모듈화 하는데 유리합니다. 일반적인 컨트랙트에서 외부 메시지 호출은 call함수에 의해 처리되고, 코드는 외부 컨트랙트에서 실행하게 됩니다.
    • delegatecall은 call과 거의 같은 기능이지만 코드가 외부 컨트랙트에서 실행되는 것이 아니라 호출한 컨트랙트에서 실행됩니다.
    • msg.sender와 msg.value 값은 변경이 되지 않습니다. 외부 라이브러리의 경우 라이브러리가 다른 컨트랙트를 호출할 때, 라이브러리를 호출한 원래 컨트랙트 주소를 알 수 있습니다. 예기치 않은 코드가 실행될 위험이 존재합니다.

     

     

    2. 문제 풀

     

    먼저 코드를 확인합니다. owner를 바꿀 수 있는 부분을 확인합니다. Fallback methods와 Method ids가 힌트로 주어졌습니다.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.6.0;
    
    contract Delegate {
    
      address public owner;
    
      constructor(address _owner) public {
        owner = _owner;
      }
    
      function pwn() public {
        owner = msg.sender;
      }
    }
    
    contract Delegation {
    
      address public owner;
      Delegate delegate;
    
      constructor(address _delegateAddress) public {
        delegate = Delegate(_delegateAddress);
        owner = msg.sender;
      }
    
      fallback() external {
        (bool result,) = address(delegate).delegatecall(msg.data);
        if (result) {
          this;
        }
      }
    }

     

    ① pwn() 

    owner를 바꾸는 부분은 pwn() 함수이다. 그리고 2번째 컨트랙트의 fallback 함수에 delegatecall이 나온다. delegatecall의 개념과 힌트를 종합해보면 Delegation 컨트랙트의 fallback 함수를 호출하고 delegatecall로 pwn() 함수를 실행시키는 것이 이번 미션을 해결하는 흐름이 될 것입니다. 

     

    ② delegatecall

    contract를 콘솔에 입력해서 abi를 확인해보면 주어진 instance는 delegate 컨트랙트가 아닌 delegation 컨트랙트이다. 

     

     

    delegatecall을 사용할 때의 그림입니다. 외부 컨트랙트의 함수를 호출하지만 실행은 내가 호출한 컨트랙트에서 실행이 됩니다. 즉, fallback 함수를 호출하고 실행시키기 위해 msg.data의 값을 넘겨주기만 하면 됩니다. 

     

     

    예제로 배우는 delegatecall 사용법  

     

    pragma solidity 0.8.6;
    
    contract Target {
        
        uint public num;
        
        event Test(uint a, uint b, address c);
        
        function test(uint _a, uint _b ) public returns (uint){
            num = _a + _b;
            emit Test(_a, _b, msg.sender);
            return _a + _b;
        }
        
        function pwn() public {
            
        }
    }
    
    contract Deligataion {
        
        uint public num;
        
        function callTest(address _contract, uint _a, uint _b) public returns (bool, bytes memory, address){
            (bool success, bytes memory data) = address(_contract).call(abi.encodeWithSignature("test(uint256,uint256)", _a, _b));
            return (success, data, _contract);
        }
        
        function delegatecallTest(address _contract, uint _a, uint _b) public returns (bool, bytes memory, address) {
            (bool success, bytes memory data) = address(_contract).delegatecall(abi.encodeWithSignature("test(uint256,uint256)", _a, _b));
            return (success, data, _contract);
        }
    }

     

    갑자기 복잡한 코드가 등장해서 당황할 수 있겠지만 잘보면 어렵지 않습니다. 위의 예제는 call과 delegatecall를 테스트 해볼 수 있으며 delgatecall만 설명하겠습니다.

     

    contract Target 

     

    test 함수를 타겟으로 설정할 것입니다. test 함수는 변수 num에 인자로 넘겨준 두 정수를 더해준 값을 반환합니다.

     

    function delegatecallTest(address _contract, uint _a, uint _b) public returns (bool, bytes memory, address) {
            (bool success, bytes memory data) = address(_contract).delegatecall(abi.encodeWithSignature("test(uint256,uint256)", _a, _b));
            return (success, data, _contract);
        }

     

    function delegatecllTest

     

    파라미터는 3가지가 필요합니다. 타겟의 컨트랙트 주소, 타겟 함수의 파라미터(uint a, b)입니다. 리턴 받는 값은 성공여부인 success, 타겟 함수의 반환값인 data, 호출된 타겟 컨트랙트의 주소입니다. 

     

    => address(_contract).delegatecall(abi.encodeWithSignature("test(uint256,uint256)", _a, _b));

     

    address() 안에는 타겟의 컨트랙트 주소가 입력되어 접근합니다.

    delegatecall() 안에는 타겟 컨트랙트의 함수 정보가 담겨야 합니다. abi.encodeWithSignature는 함수의 시그니처를 만들어줍니다. 솔리디티에 작성된 함수는 우리 눈에서 이해가 되는 함수지만 컴파일하고 전달되는 형태는 바이트 코드입니다. 함수의 이름, 파라미터 등은 Keccak (SHA-3) 해시함수로 해싱되어 16진수의 기다란 코드형태로 변환이 됩니다. 이때 해시의 첫 번째에서 4바이트를 함수의 시그니처라고 합니다. 시그니처를 통해 함수를 확인하고 불러올 수 있습니다.

     

    위에서 a, b의 값에 5와 5를 넘겨주면 Target 컨트랙트의 test 함수를 호출하여 사용하지만 Deligation 컨트랙트에서 실행되서 결과값도 이 안에서 적용이 됩니다. 즉, test 함수의 결과로 5+5 = 10의 결과가 Target 컨트랙트의 num이 아닌 Deligation 컨트랙트의 num에 저장이 됩니다.

     

    이러한 특성을 활용하여 delegatecall은 보통 상태를 바꾸지 않고 기능만을 갖다쓰는 라이브러리에 활용이 됩니다. 현재는 라이브러리를 쓸 수 있는 개선된 방법들이 나왔지만 예전에는 이렇게 활용했습니다. 이제 다시 미션으로 넘어가서 마무리를 짓겠습니다.

     


     

    ③ msg.data

     

    우리는 앞선 level에서 fallback 함수를 다뤘습니다. fallback 함수는 컨트랙트에 매칭되는 데이터나 함수가 없을 때 실행이 됩니다. fallback 함수를 실행시키는 것은 어렵지 않은데 delegatecall에 msg.data가 들어가 있습니다.

     

    • msg.data는 메시지 프로퍼티(Message Properties)입니다. 메시지 프로퍼티를 활용해 컨트랙트를 호출한 사람이 보낸 메시지를 확인하며 담겨있는 메시지 정보는 다음과 같습니다.

     

    정보 타입 설명
    data byte 호출데이터
    sender address 계약을 호출한 이더리움 주소
    value uint 계약 주소로 보낸 Ether량
    gas  uint gas limit에서 함수를 호출하고 남은 가스

     

     

    Mehod ids라는 힌트가 있었습니다. msg.datapwn() 함수의 시그니처 데이터를 전달하면 delegatecall로 pwn() 함수를 실행할 수 있을 것입니다. pwn() 함수의 시그니처 데이터를 얻는 방법은 간단합니다. 앞에 4바이트는 함수의 이름으로 만들어지므로 Keccak-256 Online에 접속해서 pwn()을 해싱해 줍니다. 앞에 4바이트 값을 가져오면 됩니다. (16진수 숫자 2개가 1바이트입니다.) 이더리움에서는 0x프로토콜을 사용하고 있으므로 앞에 0x를 붙여 완성합니다.

     

     

    pwn() 함수의 시그니처는 앞에 4바이트 값인 dd365b8b이다.

     

    이제 0xdd365b8b를 data에 넣어서 트랜잭션을 생성하면 fallback 함수가 실행되면서 delegatecall로 pwn() 함수를 실행합니다. 그 결과 Delegation 컨트랙트의 owner가 나의 주소로 바뀌었습니다. 미션 성공입니다 !

    Submit instance 버튼을 눌러 제출을 완료합니다. 수고하셨습니다.

     

    Delegation 컨트랙트의 owner가 나의 주소로 바뀌었다.

     

    목표 달성 ! 주어진 Instance의 소유권 가져오기

     


    * 현재 remix에서 시도하면 owner가 바뀌지 않고 있는 문제가 발생하고 있습니다. ethernaut의 콘솔창에서 진행해야 바뀝니다ㅠ

     

    댓글

Designed by Tistory.