Capture the ether-Fuzzy identity

problem link

pragma solidity ^0.4.21;
 
interface IName {
    function name() external view returns (bytes32);
}
 
contract FuzzyIdentityChallenge {
    bool public isComplete;
 
    function authenticate() public {
        require(isSmarx(msg.sender));
        require(isBadCode(msg.sender));
 
        isComplete = true;
    }
 
    function isSmarx(address addr) internal view returns (bool) {
        return IName(addr).name() == bytes32("smarx");
    }
 
    function isBadCode(address _addr) internal pure returns (bool) {
        bytes20 addr = bytes20(_addr);
        bytes20 id = hex"000000000000000000000000000000000badc0de";
        bytes20 mask = hex"000000000000000000000000000000000fffffff";
 
        for (uint256 i = 0; i < 34; i++) {
            if (addr & mask == id) {
                return true;
            }
            mask <<= 4;
            id <<= 4;
        }
 
        return false;
    }
}

풀이

Bruteforce를 통해 특정 패턴을 가진 계정의 PK를 찾을 수 있다. 다만 패턴의 길이가 길 수록 찾기 어려워진다. 또한, 해당 계정이 배포하는 컨트랙트 주소를 계산할 수 있다. 주소와 nonce 값을 기반으로 계산한다. 툴에서는 주로 처음 배포하는 컨트랙트 주소가 해당 패턴인 계정을 찾아낸다. 이 문제에서는 컨트랙트 주소 어느 위치에라도 badc0de 패턴이 들어가야 하고, 또한 EOA가 아닌 컨트랙트의 주소이기를 요구한다. (name을 조회해야 하기 때문)

https://github.com/10gic/vanitygen-plusplus 툴을 사용하니 좋았다. GPU로 돌릴 수도 있을 것 같지만, 사용하지 않았다. 많은 툴들이 시작 문자나 끝 문자가 특정 패턴인 것을 찾지만, 문제에서는 주소 어디에든 있기만 하면 된다. 이러면 찾는 조건이 훨씬 더 널널하여 더 빠르게 찾을 수 있다. regex 옵션을 주면 이렇게 검색할 수 있다. ./vanitygen++ -C ETH -r badc0de -F contract 와 같은 명령어로 실행했다.

Generating ETH Address
[996.52 Kkey/s][total 1336502280]                                              
ETH Pattern: badc0de                                                           
ETH Address: 0x11179c2758AD9fdbadc0de78A05C5566e3F2607F
ETH Privkey: 0x759a8ae5f46c8c873887ea8fdc8289cc4d7585bbe3489c3a17bb023ccd3d1c9e

위와같이 PK를 준다. 해당 PK로 컨트랙트를 처음 생성하면 ETH Address 로 주어진 주소로 컨트랙트를 만들 수 있다.

// SPDX-License-Identifier: MIT
 
pragma solidity ^0.8.7;
 
// use vanity eth project! https://github.com/10gic/vanitygen-plusplus
// ./vanitygen++ -C ETH -F contract -r badc0de
interface FuzzyIdentityChallenge {
    function authenticate() external;
    function isComplete() external view returns (bool);
}
 
contract FuzzyIdentitySolver {
    address payable public owner;
    FuzzyIdentityChallenge public problem;
    bytes32 public name = bytes32("smarx");
 
    constructor (address _problem) {
        owner = payable(msg.sender);
        problem = FuzzyIdentityChallenge(payable(_problem));
    }
 
    function solve() public {
        problem.authenticate();
        require(problem.isComplete(), "fail");
    }
}

다음은 공격 코드이다. bruteforce를 통해 찾은 계정으로 배포한다.

이 문제는 계정 인증을 위해 주소를 일부만 체크하여 발생한 취약점이다. 어느정도 일치하는 수준의 계정은 bruteforce로 충분히 찾아낼 수 있으므로, 전체 주소를 이용하여 비교해야 한다.


tags: writeup, blockchain, solidity, smart contract, bruteforce