teppay’s log

*について書きます

Swamp CTF2019 Writeup

はじめに

  • 就活やらなんやらが落ち着いたのでひっさしぶりにCTFをやった Part 2
  • スマートコントラクト問を初めてやった?けど楽しかった
  • もう一個も脆弱な部分はわかったけど解けないのがあって悔しい
  • スマートコントラクト問は簡単なのでも意外とSolve数が少なかったので狙い目かなと思った.

Writeup

Multi-Owner Contract [Smart Contract]

pragma solidity ^0.4.24;

contract Ownable {

    event OwnerAdded(address);
    event OwnerRemoved(address);

    address public implementation;
    mapping (address => bool) public owners;

    modifier onlyOwner() {
        require(owners[msg.sender], "Must be an owner to call this function");
        _;
    }

    /** Only called when contract is instantiated
      */
    function contructor() public payable {
        require(msg.value == 0.5 ether, "Must send 0.5 Ether");
        owners[msg.sender] = true;
    }

    /** Add an owner to the owners list
     *  Only allow owners to add other owners
     */
    function addOwner(address _owner) public onlyOwner { 
        owners[_owner] = true;
        emit OwnerAdded(_owner);
    }

    /** Remove another owner
     *  Only allow owners to remove other owners
     */
    function removeOwner(address _owner) public onlyOwner { 
        owners[_owner] = false;
        emit OwnerRemoved(_owner);
    }

    /** Remove all owners mapping and relinquish control of contract*
     */
    function renounceOwnership() public {
        assembly {
            sstore(owners_offset, 0x0)
        }
    }
    
    /** CTF helper function*
     *  Used to clean up contract and return funds*
     */
    function killContract() public onlyOwner {
        selfdestruct(msg.sender);
    }

    /** CTF helper function*
     *  Used to check if challenge is complete*
     */
    function isComplete() public view returns(bool) {
        return owners[msg.sender];
    }

}

このCTFには複数のSmart Contract問があって,すべての問題においてContractをisComplete()trueを返す状態にすることがFlagの条件.

この問題は簡単でconstructor()の書き方が間違っている. Solidityにおいて,constructorメソッドは特殊メソッドでコントラクトがDeployされる際に1度のみ実行され(通常のコンストラクタと同様),それ以降実行することができない. コンストラクタはよくそのコントラクトのownerを指定する処理が書かれるなど重要なメソッドであるため,通常のメソッドと違い以下のように特殊な書き方をする.

constructor() public{
     /**/         
}

ただしこのコントラクトでは書き方が間違っているため通常のメソッドと同様に実行が可能である. これを実行することで自分のアカウントをownerに追加できるためisCompleteの条件を満たすことができる flag{3v3ryb0dy5_hum4n_r34d_c10s31y}

Hash Slinging Slasher [Smart Contract]

pragma solidity ^0.4.24;

contract HashSlingingSlasher {
    
    bytes32 answer = 0xed2a0ca74e236c332625ad7f4db75b63d2a2ee7e3fe52c2c93c8dbc4e06906d1;
    
    constructor() public payable {
        require(msg.value == 0.5 ether, "Must send 0.5 Ether");        
    }

    /** Guess the hashed number. Refunds ether if guessed correctly*
     */
    function guess(uint16 number) public payable {
        require(msg.value == 0.25 ether, "Must send 0.25 Ether");
        if(keccak256(abi.encodePacked(number)) == answer) {
            msg.sender.transfer(address(this).balance);
        }
    }
    
    /** Returns balance of this contract*
     */
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    /** CTF helper function*
     *  Used to check if challenge is complete*
     */
    function isComplete() public view returns (bool) {
        return address(this).balance == 0;
    }
    
}

このコントラクトはハッシュ当てゲーム? SHA3でハッシュ化したときにソースコードにハードコーディングされているハッシュ値と等しくなる数値を入力できればクリア.

このコントラクトではguessメソッドで数値を入力するわけだが,このメソッドを実行するためには1回につき0.25etherかかるため,一見ブルートフォース対策がされているように見える.しかしハッシュ値はすでに知っているためいわゆるオフライン攻撃が可能になっている.またguessメソッドの入力値はuint16であるためたかだか65536(=216)回の試行で答えを求めることができる.

以下のスクリプトで入力値を求めると13337であるということがわかったのでguessメソッドを実行してあげれば終わり

from web3 import Web3
for i in range(2**16):
    if Web3.sha3(i).hex() == '0xed2a0ca74e236c332625ad7f4db75b63d2a2ee7e3fe52c2c93c8dbc4e06906d1':
        print(i)

flag{0n_th3_b10ckch41n_3v3ryth1ng_15_pub1ic}